从〇 搭建PO模式的Web UI自动化测试框架

Page Object模式简介

核心思想

将页面元素和操作行为封装在独立的类中,形成页面对象(Page Object)。每个页面对象代表应用程序中的一个特定页面或组件。

优点:

代码复用性高

页面对象可以在多个测试用例中复用。

易于维护

如果页面布局或元素发生变化,只需要修改对应的页面对象类,而不需要修改所有相关的测试用例。


项目结构介绍

在这里插入图片描述

allure-results/:

  • 作用: 存放 Allure 生成的测试结果文件。Allure 从这里读取数据来生成报告。
    内容示例: 包含测试结果的 JSON 文件、截图等附件。

pages/:

  • 作用:存放Page Object类。Page Object类将页面元素及其操作封装起来,每个类对应一个页面或组件。这种封装使得测试代码更为简洁易读,并减少了页面变更时的修改量。

  • 示例:

    • base_page.py: 定义了一个基础的Page类,提供了页面操作的通用方法,如查找元素、点击、输入文本等。其他页面类都会继承这个类。
    import allure
    from selenium.common import TimeoutException
    from selenium.webdriver.remote.webdriver import WebDriver
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    
    
    class BasePage:
        def __init__(self, driver: WebDriver):
            self.driver = driver
            self.timeout = 5
    
        def find_element(self, locator):
            """
            获取元素的手柄
            :param locator:指定元素的定位器
            :return:元素手柄
            """
            # presence_of_element_located:等待某个元素出现在 DOM 中
            # visibility_of_element_located:等待某个元素不仅存在于 DOM 中,而且是可见的。
            try:
                # 尝试等待元素的可见性
                return WebDriverWait(self.driver, self.timeout).until(EC.visibility_of_element_located(locator))
            except TimeoutException:
                # 如果元素的可见性等待超时,继续等待元素的存在
                return WebDriverWait(self.driver, self.timeout).until(EC.presence_of_element_located(locator))
    
        def element_exists(self, locator):
            """
            用显示等待判断指定元素是否存在
            :param locator:待判断元素的定位器
            :return:True/False
            """
            try:
                self.find_element(locator)
                return True
            except TimeoutException:
                return False
    
        def upload_file(self, file_path, locator):
            """
            公共的上传文件函数
            :param file_path (str):要上传的文件的路径
            :param locator (tuple):上传元素的定位器
            :return: 无
            """
            with allure.step(f"上传文件:{file_path}"):
                upload_element = self.find_element(locator)
                upload_element.send_keys(file_path)
    
        def get_text(self, locator):
            text_content = self.find_element(locator).text
            return text_content
    
        def click(self, locator):
            """
            点击指定的元素
            :param locator: 指定元素的定位器
            :return:
            """
            with allure.step(f"点击指定元素:{locator}"):
                self.find_element(locator).click()
    
        def double_click(self, locator):
            """
            点击指定的元素
            :param locator: 指定元素的定位器
            :return:
            """
            with allure.step(f"双击指定元素:{locator}"):
                self.find_element(locator).double_click()
    
        def context_click(self, locator):
            """
            右键指定的元素
            :param locator: 指定元素的定位器
            :return:
            """
            with allure.step(f"右键指定元素:{locator}"):
                self.find_element(locator).context_click()
    
        def send_keys(self, locator, text):
            """
            收入数据
            :param locator: 指定元素的定位器
            :param text: 待输入的数据
            :return:
            """
            with allure.step(f"在{locator}中输入:{text}"):
                self.find_element(locator).send_keys(text)
    
        def get_title(self):
            """
            获取当前页面表头
            :return:
            """
            return self.driver.title
    
    
    • baidu_page.py: 这个文件是一个具体的Page Object类的示例,封装了登录页面的操作。通常会包含元素定位和一些基本的操作方法,如输入输入、点击搜索按钮等。
    import time
    import allure
    from selenium.webdriver.common.by import By
    from .base_page import BasePage
    
    
    class LoginPage(BasePage):
        # 元素:element
        # 定位器:positioner
        ep_INPUT = (By.XPATH, '//*[@id="kw"]')
        ep_SEARCH_BUTTON = (By.XPATH, '//*[@id="su"]')
        ep_ASSERT_TEXT = (By.XPATH, '//*[@id="1"]/div/section/div[1]/span')
        ep_INPUT_IMAGE = (By.XPATH, '//*[@id="form"]/span[1]/span[1]')
        ep_SELECT_FILE = (By.XPATH, '//*[@id="form"]/div/div[2]/div[2]/input')
        ep_IMAGE_PATH = 'D:\\UITestProject\\20240815175707543.jpg'
        ep_ASSERT_IMAGE = (By.XPATH, '//*[@id="app"]/div/div[1]/div/div[1]/div/div/div[2]/div[1]')
    
        # @allure.step("输入搜素text") # 用于记录测试用例中的具体步骤
        def input_value(self, value):
            """
            输入搜索值
            :param value:
            :return:
            """
            self.send_keys(self.ep_INPUT, value)
    
        # @allure.step("点击搜索按钮") # 用于记录测试用例中的具体步骤
        def click_search(self):
            """
            点击搜索按钮
            :return:
            """
            self.click(self.ep_SEARCH_BUTTON)
    
        # @allure.step("获取判断元素的text")
        def get_assert_text_text(self):
            return self.get_text(self.ep_ASSERT_TEXT)
    
        # @allure.step("上传图片")  # 用于记录测试用例中的具体步骤
        def upload_image(self):
            self.click(self.ep_INPUT_IMAGE)
            self.upload_file(self.ep_IMAGE_PATH, self.ep_SELECT_FILE)
    
        def get_assert_image_text(self):
            print(self.get_text(self.ep_ASSERT_IMAGE))
            return self.get_text(self.ep_ASSERT_IMAGE)
    
    

tests/:

  • 作用: 存放所有的测试用例文件。每个测试文件通常以 test_ 开头,包含测试函数和测试类。
    • 示例:
      • test_baidu.py:包含针对百度功能的测试用例。
    import allure
    import pytest
    from pages.baidu_page import LoginPage
    
    
    # @pytest.mark.usefixtures("setup") # 在测试用例之前和之后执行
    @allure.feature("百度搜索功能")
    class TestLogin:
        # @pytest.mark.skip(reason="跳过该测试用例!") # 跳过该测试用例!
        # @pytest.mark.skipif(sys.platform == "win32", reason="Windows上不运行该用例") # 根据条件判断是否跳过该测试用例
        # @pytest.mark.run(order=1) # 控制运行顺序【优先级】,从小到大依次执行
        @allure.title("搜索文本功能")  # 用于给测试用例设置一个自定义的标题,替代默认的函数名称。
        @pytest.mark.parametrize("value, text", [("猪猪", "其他人还搜"), ("狗子", "相关动物")])  # 参数化
        def test_search_text(self, value, text):
            login_page = LoginPage(self.driver)
            login_page.input_value(value)
            login_page.click_search()
            assert login_page.get_assert_text_text() == text
            assert login_page.get_title() == f"{value}_百度搜索"
    
        @allure.title("搜索图片功能")  # 用于给测试用例设置一个自定义的标题,替代默认的函数名称。
        @allure.description("测试是否可以搜索出与上传图片有关的内容")  # 为测试用例添加详细描述,帮助更好地理解测试的目的和背景。
        def test_search_image(self):
            login_page = LoginPage(self.driver)
            login_page.upload_image()
            assert login_page.get_assert_image_text() == "文字提取"
    		
    

utils/:

  • 作用:作用: 存放工具类和辅助功能的脚本。通常包括 WebDriver 的工厂类、常用的功能函数等。
  • 示例:
    • driver_factory.py:创建和管理 WebDriver 实例的工厂类。
from selenium import webdriver

def get_driver(browser_name="chrome"):
    # 判断启动的浏览器,不区分大小写
    if browser_name.lower() == "chrome":
        options = webdriver.ChromeOptions()
        # 添加启动时最大化窗口的参数
        options.add_argument("--start-maximized")
        return webdriver.Chrome(options=options)

    elif browser_name.lower() == "firefox":
        options = webdriver.FirefoxOptions()
        options.add_argument("--start-maximized")
        return webdriver.Firefox(options=options)

    elif browser_name.lower() == "edge":
        options = webdriver.EdgeOptions()
        options.add_argument("--start-maximized")
        return webdriver.Edge(options=options)

    else:
        raise ValueError(f"不支持该浏览器: {browser_name}")
			

conftest.py:

  • 作用:用于设置和清理测试环境、初始化 WebDriver 实例等。

    import allure
    import pytest
    from utils.driver_factory import get_driver
    
    
    # 在每个测试类之前执行一次,需要手动添加注解后才能生效
    @pytest.fixture(scope="class")
    def setup(request):
        # 使用 get_driver 函数创建一个 WebDriver,可以根据需求修改浏览器类型
        # driver = get_driver(browser_name="chrome")
        driver = get_driver(browser_name="edge")
        # 将创建的 WebDriver 实例赋值给测试类,以便测试用例可以使用
        request.cls.driver = driver
        # yield 语句之前的代码是测试前置条件,yield 之后的代码是测试后置条件
        yield
        driver.quit()
    
    
    # 在每个测试用例之前执行一次,不需要额外给测试用例添加注解
    @pytest.fixture(autouse=True)
    def test_case_setup(request):
        # 将创建的 WebDriver 实例赋值给测试类,以便测试用例可以使用
        driver = get_driver(browser_name="chrome")
        request.cls.driver = driver
        # 打开指定链接
        driver.get("https://www.baidu.com")
        # yield 语句之前的代码是测试前置条件,yield 之后的代码是测试后置条件
        yield
        # 在测试用例执行完毕后,关闭浏览器并退出 WebDriver
        driver.close()
    
    
    # 生成测试报告的钩子函数
    @pytest.hookimpl(tryfirst=True, hookwrapper=True)
    def pytest_runtest_makereport(item, call):
        # 先执行测试
        outcome = yield
        report = outcome.get_result()
    
        # 如果测试失败并且是一个 "call" 状态(表示实际运行测试代码时出错)
        # if report.when == "call" and report.failed:
        # 测试通过或不通过都截图
        if report.when == "call":
            # 通过 request 对象获取 driver 实例
            driver = item.funcargs['request'].cls.driver
            # 截图并附加到 Allure 报告中
            try:
                allure.attach(driver.get_screenshot_as_png(),
                              name="测试结果截图",
                              attachment_type=allure.attachment_type.PNG)
            except Exception as e:
                # 将错误日志作为附件添加到 Allure 报告中
                allure.attach(f"截图失败: {e}",
                              name="截图失败日志",
                              attachment_type=allure.attachment_type.TEXT)
    
    

pytest.ini:

  • 作用: 配置 pytest 的行为,包括指定测试目录、文件名模式、标记等。
[pytest]
# --continue-on-collection-errors 测试用例报错后,仍然继续运行
# --alluredir=./allure-results:指定生成的测试数据存储的位置
addopts = --continue-on-collection-errors --alluredir=./allure-results

# 指定 pytest 搜索测试用例的根目录
# 所有测试文件和目录都会在 `tests` 目录下被自动发现和执行
testpaths = tests
# 指定测试文件的命名模式
# pytest 只会识别文件名以 `test_` 开头的 Python 文件作为测试文件
python_files = test_*.py

# 指定测试类的命名模式
# pytest 只会识别以 `Test` 开头的类名作为测试类
python_classes = Test*

# 指定测试函数的命名模式
# pytest 只会识别以 `test_` 开头的函数名作为测试函数
python_functions = test_*
	

requirements.txt

  • 作用:当前项目中所使用到的第三方库

    allure-pytest==2.13.5
    allure-python-commons==2.13.5
    attrs==24.2.0
    certifi==2024.7.4
    cffi==1.17.0
    colorama==0.4.6
    exceptiongroup==1.2.2
    h11==0.14.0
    idna==3.7
    iniconfig==2.0.0
    outcome==1.3.0.post0
    packaging==24.1
    pluggy==1.5.0
    pycparser==2.22
    PySocks==1.7.1
    pytest==8.3.2
    selenium==4.23.1
    sniffio==1.3.1
    sortedcontainers==2.4.0
    tomli==2.0.1
    trio==0.26.2
    trio-websocket==0.11.1
    typing_extensions==4.12.2
    urllib3==2.2.2
    websocket-client==1.8.0
    wsproto==1.2.0
    
    

运行

运行测试:

pytest

查看Allure测试报告

allure serve allure-results

测试报告示例

在这里插入图片描述

  • 13
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
非常感谢您的提问。我可以回答您的问题。以下是基于TESTng框架的PO模式UI自动化测试框架的代码示例: ``` public class LoginPage { private WebDriver driver; private By usernameLocator = By.id("username"); private By passwordLocator = By.id("password"); private By loginButtonLocator = By.id("loginButton"); public LoginPage(WebDriver driver) { this.driver = driver; } public void setUsername(String username) { driver.findElement(usernameLocator).sendKeys(username); } public void setPassword(String password) { driver.findElement(passwordLocator).sendKeys(password); } public void clickLoginButton() { driver.findElement(loginButtonLocator).click(); } } public class HomePage { private WebDriver driver; private By welcomeMessageLocator = By.id("welcomeMessage"); public HomePage(WebDriver driver) { this.driver = driver; } public String getWelcomeMessage() { return driver.findElement(welcomeMessageLocator).getText(); } } public class TestBase { protected WebDriver driver; @BeforeClass public void setUp() { driver = new ChromeDriver(); driver.manage().window().maximize(); } @AfterClass public void tearDown() { driver.quit(); } } public class LoginTest extends TestBase { private String baseUrl = "http://example.com"; private String username = "testuser"; private String password = "testpass"; @Test public void testLogin() { driver.get(baseUrl); LoginPage loginPage = new LoginPage(driver); loginPage.setUsername(username); loginPage.setPassword(password); loginPage.clickLoginButton(); HomePage homePage = new HomePage(driver); String welcomeMessage = homePage.getWelcomeMessage(); Assert.assertEquals(welcomeMessage, "Welcome, " + username + "!"); } } ``` 这个框架使用了Page Object模式,将页面元素和操作封装在了对应的Page类中,使得测试用例更加清晰易读。同时,使用了TESTng框架来管理测试用例的执行顺序和测试结果的输出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值