python+playwright+pytest+allure+pom+yaml实现UI自动化测试

一. 演示项目源码

https://gitee.com/giteetangll/playwright-demo

二、UI自动化分层设计

python+playwright+pytest+allure+pom+yaml实现UI自动化测试_第1张图片
Auth:登录认证保存后的认证信息
BasePage:封装playwright的基础方法
BuildInLibrary:环境变量存放文件夹,可进行用例参数关联
Common:存放公共方法抽离文件夹
Config:配置文件存放文件夹
Logs:存放断言失败的记录
Pages:存放页面对象文件
TestCases:存放测试用例
TestDatas:存放测试数据
TestFiles:存放测试需要使用的文件
TestReport:存放测试报告
Utils:存放工具的封装
runner.py:项目运行文件
pytest.ini :pytest配置文件

三、po设计模型+分层设计思想

3.1BasePage层

封装常用的元素方法,后续可以自定义日志输出

# -*- coding:utf-8 -*-
"""
describe:
Author:tang
Email:[email protected]
Time: 2023/4/18
"""

from playwright.sync_api import expect, Page
from BuildInLibrary.BuildInLibrary import BuildInLibrary


class BasePage:

    def __init__(self, page: Page):
        self.page = page

    def _goto_url(self, url):
        """打开网页"""
        self.page.goto(url)

    def _click(self, locator, frame_locator=None):
        """
        点击元素
        :param locator: 传入元素定位器
        :param frame_locator: 传入frame框架的的定位器,如果没有传入,则一般点击
        :return:
        """
        try:
            if frame_locator is not None:
                self.page.frame_locator(frame_locator).locator(locator).click()
            else:
                self.page.click(locator)
        except Exception as e:
            print(e)

    def _fill(self, locator, value, frame_locator=None):
        """
        定位元素,输入内容
        :param locator:传入元素定位器
        :param value:传入输入的值
        :param frame_locator: 传入frame框架
        :return:
        """
        value = BuildInLibrary().repalce_parameter(value)
        try:
            if frame_locator is not None:
                self.page.frame_locator(selector=frame_locator).locator(selector_or_locator=locator).fill(value)
            else:
                self.page.fill(selector=locator, value=value)
        except Exception as e:
            print(e)

    def _type(self, locator, value, frame_locator=None):
        """
        模拟人工输入,一个键一个键的输入
        :param locator:传入元素定位器
        :param value:传入输入的值
        :param frame_locator: 传入frame框架
        :return:
        """
        value = BuildInLibrary().repalce_parameter(value)
        try:
            if frame_locator is not None:
                self.page.frame_locator(selector=frame_locator).locator(selector_or_locator=locator).type(text=value,delay=100)
            else:
                self.page.type(selector=locator, text=value,delay=100)
        except Exception as e:
            print(e)


    def _ele_to_be_visible(self, locator):
        """断言元素可见"""
        return expect(self.page.locator(locator)).to_be_visible()

    def _ele_is_checked(self,selector):
        """判断元素是否被选选中"""
        return self.page.is_checked(selector)

    def _browser_operation(self, reload=False, forward=False, back=False):
        """浏览器操作,reload 刷新,forward 前进,back 后退"""
        if reload:
            self.page.reload()
        if back:
            self.page.go_back()
        if forward:
            self.page.go_forward()

    def screenshot(self, path, full_page=True, locator=None):
        """截图功能,默认截取全屏,如果传入定位器表示截取元素"""
        if locator is not None:
            self.page.locator(locator).screenshot(path=path)
            return path
        self.page.screenshot(path=path, full_page=full_page)
        return path

3.2 BuildInLibrary层

设计想法是为了实现用例关联参数,设置和替换环境变量。

# -*- coding:utf-8 -*- 
"""
describe:设置全局环境变量
Author:tang
Email:[email protected]
Time: 2023/4/19 
"""
import random, time, re


class BuildInLibrary():
    glob_parameter = {}  # 存全局变量

    def set_glob_parameter(self, key, value):
        """把value的值放入变量名 key 中,"""
        # 1.提取 key   ##{{first_phone}}
        parameter_key = re.fullmatch(r'{{(\w+)}}', key).group(1)
        # 2.保存参数
        self.glob_parameter[parameter_key] = value
        return self.glob_parameter.get(parameter_key)

    def get_glob_parameter(self, key):
        self.glob_parameter['timestamp'] = str(int(time.time() * 1000))
        self.glob_parameter["timetime"] = str(int(time.time()))
        self.glob_parameter['random_phone'] = "1" + \
                                              str(random.randint(3, 9)) + \
                                              str(random.randint(0, 9)) + \
                                              time.strftime('%d%H%M%S')
        return self.glob_parameter.get(key)

    def repalce_parameter(self, text):
        """替换参数--> 可以替换多个~,满足{{$参数}}规则的会被替换"""
        parameter_key = re.findall(r'{{\$(\w+)}}', text)
        for param in parameter_key:
            value = self.get_glob_parameter(param)
            to = rf"{value}"
            text = re.sub(rf'{{{{\${param}}}}}', lambda m: to, text)
        return text


if __name__ == "__main__":
	pass

3.3 Common层

设计想法是为了将常用公共代码进行抽离,使测试脚本更加简洁方便

# -*- coding:utf-8 -*- 
"""
describe:allure常用方法抽离,截图方法实现
Author:tang
Email:[email protected]
Time: 2023/4/18
"""
import os.path, allure, pytest, functools
from Config.Config import Config


class PrettyAllure(object):

    @classmethod
    def PrettyAllureCase(cls, page, CaseData):
        allure.dynamic.feature(CaseData.get("模块"))
        allure.dynamic.story(CaseData.get("功能"))
        allure.dynamic.severity(CaseData.get("优先级"))
        allure.dynamic.title(f'{CaseData.get("用例编号")}_{CaseData.get("用例标题")}')
        if CaseData.get("是否执行") != "Y":
            allure.dynamic.description("用例指定跳过")
            pytest.skip("用例指定跳过")

    @classmethod
    def PrettyAllureScreenShot(cls, page, CaseData):
        filename = os.path.join(Config.test_screenshot_dir, f"{CaseData.get('用例标题')}.png")
        page.screenshot(path=filename)
        allure.attach.file(source=filename, name=CaseData.get('用例标题'), attachment_type=allure.attachment_type.PNG)

    @classmethod
    def PrettyAllureWarpper(cls, func):
        """装饰器函数"""

        @functools.wraps(func)
        def inner(*args, **kwargs):
            # 添加用例信息
            cls.PrettyAllureCase(page=kwargs.get("page"), CaseData=kwargs.get("CaseData"))  # 如何获取case_data?
            r = func(*args, **kwargs)  # 运行用例
            # 添加截图
            cls.PrettyAllureScreenShot(page=kwargs.get("page"), CaseData=kwargs.get("CaseData"))
            return r

        return inner

if __name__ == '__main__':
    pass

3.4 Config层

设计思路是为了实现配置化,方便管理,为后续持续集成铺垫。比如:URL配置,日志的配置,邮件服务配置,企业微信聊天机器人配置等

# -*- coding:utf-8 -*- 
"""
describe:配置文件
Author:tang
Email:[email protected]
Time: 2023/4/17 
Software: PyCharm
"""
import os


class Config:
    # 项目地址
    url = "http://124.223.40.245:83"

    # 项目根目录
    root_dir = os.path.split(os.path.split(__file__)[0])[0]
    test_cases_dir = root_dir + os.path.sep + "TestCases"
    test_files_dir = root_dir + os.path.sep + "TestFiles"
    test_datas_dir = root_dir + os.path.sep + "TestDatas"
    test_report_dir = root_dir + os.path.sep + "TestReport" + os.path.sep + "AllureReport"
    test_result_dir = root_dir + os.path.sep + "TestReport" + os.path.sep + "AllureResult"
    test_screenshot_dir = root_dir + os.path.sep + "TestReport" + os.path.sep + "Screenshot"
    logs = root_dir + os.path.sep + "Logs"

    # 权限认证目录
    auth_dir = root_dir + os.path.sep + "Auth"

    #浏览器
    browser = "chrome"

if __name__ == '__main__':
    print(Config.root_dir)
    print(Config.test_cases_dir)

3.5 Logs层

设计思路是存放日志文件的目录,目前只会记录断言失败的用例信息和数据

TestCases/Test_login.py::TestLogin::test_login[CaseData0] ({'用例编号': 'Login01', '模块': '用户', '功能': '登录', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '密码错误登录失败', '前置登录': {'username': 'admin', 'password': '123456'}, '账号': '13800138006', '密码': '1234567', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/login.html', '断言元素定位': '//div[text()="密码错误!"]'})
TestCases/Test_login.py::TestLogin::test_login[CaseData0] ({'用例编号': 'Login01', '模块': '用户', '功能': '登录', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '密码错误登录失败', '前置登录': {'username': 'admin', 'password': '123456'}, '账号': '13800138006', '密码': '1234567', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/login.html', '断言元素定位': '//div[text()="密码错误!"]'})
TestCases/Test_login.py::TestLogin::test_login[CaseData0] ({'用例编号': 'Login01', '模块': '用户', '功能': '登录', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '密码错误登录失败', '前置登录': {'username': 'admin', 'password': '123456'}, '账号': '13800138006', '密码': '1234567', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/login.html', '断言元素定位': '//div[text()="密码错误!"]'})
TestCases/Test_login.py::TestLogin::test_login[CaseData0] ({'用例编号': 'Login01', '模块': '用户', '功能': '登录', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '密码错误登录失败', '前置登录': {'username': 'admin', 'password': '123456'}, '账号': '13800138006', '密码': '1234567', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/login.html', '断言元素定位': '//div[text()="密码错误!"]'})
TestCases/Test_login.py::TestLogin::test_login[CaseData1] ({'用例编号': 'Login02', '模块': '用户', '功能': '登录', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '登录成功', '前置登录': {'username': 'admin', 'password': '123456'}, '账号': '13800138006', '密码': '1234568', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/login.html', '断言元素定位': 'a[title = "退出"]'})
TestCases/Test_login.py::TestLogin::test_login[CaseData1] ({'用例编号': 'Login02', '模块': '用户', '功能': '登录', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '登录成功', '前置登录': {'username': 'admin', 'password': '123456'}, '账号': '13800138006', '密码': '1234568', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/login.html', '断言元素定位': 'a[title = "退出"]'})
TestCases/Test_login.py::TestLogin::test_login[CaseData1] ({'用例编号': 'Login02', '模块': '用户', '功能': '登录', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '登录成功', '前置登录': {'username': 'admin', 'password': '123456'}, '账号': '13800138006', '密码': '1234568', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/login.html', '断言元素定位': 'a[title = "退出"]'})
TestCases/Test_login.py::TestLogin::test_login[CaseData1] ({'用例编号': 'Login02', '模块': '用户', '功能': '登录', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '登录成功', '前置登录': {'username': 'admin', 'password': '123456'}, '账号': '13800138006', '密码': '1234568', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/login.html', '断言元素定位': 'a[title = "退出"]'})
TestCases/Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
TestCases/Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
TestCases/Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
TestCases/Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
TestCases/Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
TestCases/Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
TestCases/Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
TestCases/Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
TestCases/Test_Login.py::TestLogin::test_login[CaseData0] ({'用例编号': 'Login01', '模块': '用户', '功能': '登录', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '密码错误登录失败', '账号': '13800138006', '密码': '1234567', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/login.html', '断言元素定位': '//div[text()="密码错误!"]'})
Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
TestCases/Test_Login.py::TestLogin::test_login[CaseData0] ({'用例编号': 'Login01', '模块': '用户', '功能': '登录', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '密码错误登录失败', '账号': '13800138006', '密码': '1234567', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/login.html', '断言元素定位': '//div[text()="密码错误!"]'})
TestCases/Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
TestCases/Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
TestCases/Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})
TestCases/Test_Register.py::TestRegister::test_register[CaseData0] ({'用例编号': 'Register01', '模块': '用户', '功能': '注册', '优先级': 'blocker', '是否执行': 'Y', '用例标题': '注册成功', '用户名': '{{$random_phone}}', '密码': '123456', '确认密码': '123456', '验证码': '8888', 'url地址': 'http://124.223.40.245:83/index.php/Home/user/reg.html', '断言元素定位': 'a[title="退出"]'})

3.6 Pages层

设计思路是为了存放页面对象,每个页面单独进行存放,页面属性和行为与用例和数据区分,便于后期维护

# -*- coding:utf-8 -*-
"""
describe:登录页面
Author:tang
Email:[email protected]
Time: 2023/4/17
Software: PyCharm
"""
import allure
from BasePage.BasePage import BasePage


class LoginPage(BasePage):
    # 元素定位器
    __username = "#username"
    __password = "#password"
    __verify_code = "#verify_code"
    __login_button = 'a[name="sbtbutton"]'
    __button_logout = 'a[title="退出"]'

    @allure.step("打开登录页面")
    def goto_login(self, url):
        self._goto_url(url)

    @allure.step("输入账号")
    def fill_username(self, value):
        self._fill(self.__username, value)

    @allure.step("输入密码")
    def fill_password(self, value):
        self._fill(self.__password, value)

    @allure.step("输入验证码")
    def fill_verify_code(self, value):
        self._fill(self.__verify_code, value)

    @allure.step("点击登录按钮")
    def click_login_button(self):
        self._click(self.__login_button)

    @allure.step("点击安全退出按钮")
    def click_button_logout(self):
        self._click(self.__button_logout)

    def browser_operation(self, reload=True, forward=False, back=False):
        self._browser_operation(reload=reload, forward=forward, back=back)
# -*- coding:utf-8 -*- 
"""
describe:注册页面
Author:tang
Email:[email protected]
Time: 2023/4/20 
"""
import allure

from BasePage.BasePage import BasePage


class RegisterPage(BasePage):
    __username = "#username"
    __verify_code = 'input[placeholder="图像验证码"]'
    __password = "#password"
    __password2 = "#password2"
    __checktxt = "#checktxt"
    __btn_agree = ".regbtn.J_btn_agree"

    @allure.step("前往注册页面")
    def goto_register(self, url):
        self._goto_url(url)

    @allure.step("输入用户名")
    def type_username(self, username):
        self._type(self.__username, username)

    @allure.step("输入验证码")
    def type_verify_code(self, verify_code):
        self._type(self.__verify_code, verify_code)

    @allure.step("输入密码")
    def type_password(self, password):
        self._type(self.__password, password)

    @allure.step("输入确认密码")
    def type_password2(self, password2):
        self._type(self.__password2, password2)

    @allure.step("勾选同意")
    def click_checktxt(self):
        if not self._ele_is_checked(self.__checktxt):
            self._click(self.__checktxt)

    @allure.step("点击同意注册按钮")
    def click_btn_agree(self, ):
        self._click(self.__btn_agree)

    @allure.step("点击断言元素")
    def click_ele(self,locator):
        self._click(locator)

3.7 TestCases 层

设计思路是为了存放测试用例,使得用例和页面对象分开。维护时,只需要去维护page层即可

import allure, os
import pytest
from Pages.LoginPage.LoginPage import LoginPage
from Pages.MyAccountPage.MyAccountPage import MyAccountPage
from Utils.ReadYaml import ReadYaml
from Common.AllurePretty import PrettyAllure
from Config.Config import Config


class TestLogin:

    @pytest.mark.run(order=1)
    @PrettyAllure.PrettyAllureWarpper
    @pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "TestLoginData.yaml")).read())
    def test_login(self, page, CaseData: dict):
        new_page = LoginPage(page)
        # PrettyAllure(page, CaseData).PrettyAllureCase()
        new_page.goto_login(CaseData["url地址"])
        new_page.fill_username(CaseData["账号"])
        new_page.fill_password(CaseData["密码"])
        new_page.fill_verify_code(CaseData["验证码"])
        new_page.click_login_button()
        MyAccountPage(page).logout_to_be_visible(CaseData["断言元素定位"])

# -*- coding:utf-8 -*- 
"""
describe:注册用例
Author:tang
Email:[email protected]
Time: 2023/4/20 
"""
import time
import allure, os
import pytest
from Pages.LoginPage.LoginPage import LoginPage
from Pages.RegisterPage.RegisterPage import RegisterPage
from Pages.MyAccountPage.MyAccountPage import MyAccountPage
from Utils.ReadYaml import ReadYaml
from Common.AllurePretty import PrettyAllure
from Config.Config import Config


class TestRegister:

    @pytest.mark.smoking
    @pytest.mark.run(order=0)
    @PrettyAllure.PrettyAllureWarpper
    @pytest.mark.parametrize("CaseData", ReadYaml(os.path.join(Config.test_datas_dir, "TestRegisterData.yaml")).read())
    def test_register(self, page, CaseData: dict):
        rp = RegisterPage(page)
        rp.goto_register(url=CaseData["url地址"])
        rp.type_username(username=CaseData["用户名"])
        rp.type_verify_code(CaseData["验证码"])
        rp.type_password(CaseData["密码"])
        rp.type_password2(CaseData["确认密码"])
        rp.click_checktxt()
        rp.click_btn_agree()
        MyAccountPage(page).logout_to_be_visible(CaseData["断言元素定位"])
        rp.click_ele(CaseData["断言元素定位"])

3.8 TestDatas 层

设计思路是为了存放业务数据,使数据和业务区分开来,实现数据驱动

-
  用例编号: Register01
  模块: 用户
  功能: 注册
  优先级:  blocker
  是否执行: Y
  用例标题: 注册成功
  用户名: "{{$random_phone}}"
  密码: "123456"
  确认密码: "123456"
  验证码: "8888"
  url地址: /index.php/Home/user/reg.html
  断言元素定位: a[title="退出"]

-
  用例编号: Register02
  模块: 用户
  功能: 注册
  优先级:  blocker
  是否执行: Y
  用例标题: 两次输入的密码不一致,注册失败
  用户名: "{{$random_phone}}"
  密码: "123456"
  确认密码: "123456789"
  验证码: "8888"
  url地址: /index.php/Home/user/reg.html
  断言元素定位: .layui-layer-content.layui-layer-padding

-
  用例编号: Register03
  模块: 用户
  功能: 注册
  优先级:  blocker
  是否执行: Y
  用例标题: 重读手机号,注册失败
  用户名: "13811111111"
  密码: "123456"
  确认密码: "123456"
  验证码: "8888"
  url地址: /index.php/Home/user/reg.html
  断言元素定位: .layui-layer-content.layui-layer-padding

3.8 TestFiles层

设计思路是为了单独存放测试业务的所需文件

3.9 TestReport层

设计思路是为了存放测试报告、测试数据和测试据图的
python+playwright+pytest+allure+pom+yaml实现UI自动化测试_第2张图片

3.10 Utils 层

设计思路是为了存放常用工具的封装,比如excel文件读写,yaml文件读写,数据库操作等

# -*- coding:utf-8 -*- 
"""
describe:读取yaml文件
Author:tang
Email:[email protected]
Time: 2023/4/17 
Software: PyCharm
"""

import yaml, os
from Config.Config import Config


class ReadYaml(object):

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

    def read(self):
        with open(file=self.filename, mode="r", encoding='utf8', ) as f:
            data = f.read()
        data_yaml = yaml.load(data, Loader=yaml.FullLoader)
        for value in data_yaml:
            value["url地址"] = Config.url + value["url地址"]
        return data_yaml


if __name__ == '__main__':
    pass

  

3.11 pytest.ini 文件

pytest配置文件

[pytest]
# 配置pytest命令行运行参数
# 空格分隔,可添加多个命令行参数 -所有参数均为插件包的参数
# addopts = -s
# 配置测试搜索的路径
# 当前目录下的TestCases文件夹 -可自定义
testpaths = ./TestCases
# 配置测试搜索的文件名
# 当前目录下的testcase文件夹下,以test_开头,以.py结尾的所有文件 -可自定义
python_files = test_*.py *_test.py
# 配置测试搜索的测试类名
# 当前目录下的testcase文件夹下,以test_开头,以.py结尾的所有文件中,以Test开头的类 -可自定义
python_classes = Test*
# 配置测试搜索的测试函数名
# 当前目录下的testcase文件夹下,以test开头,以.py结尾的所有文件中,以Test_开头的类内,以test_开头的方法 -可自定义
python_functions = test*
markers =
   smoking: 冒烟

3.12 runner.py文件

项目运行入口文件

import os
import pytest
from Config.Config import Config

if __name__ == '__main__':
   AllureReport = Config.test_report_dir
   AllureResult = Config.test_result_dir
   Screenshot = Config.test_screenshot_dir
   os.system(f"del {os.path.join(Screenshot,'*.png')}")
   pytest.main(["-v", "-s", '--reruns=3', f'--alluredir={AllureResult}', "--clean-alluredir"])
   os.system(f'allure generate {AllureResult} -o {AllureReport} --clean')

四、运行结果展示

python+playwright+pytest+allure+pom+yaml实现UI自动化测试_第3张图片

你可能感兴趣的:(UI自动化,python,pytest,开发语言)