1. 项目概述:为什么选择 Python + pytest 做 UI 自动化?

如果你正在为重复的手工点击、繁琐的回归测试而头疼,或者团队里还在用着维护成本高、可读性差的“脚本堆”,那么今天聊的这个组合,绝对值得你花时间研究一下。我用了快十年的 Python 做自动化,从早期的 unittest 到 nose,再到现在的 pytest,结合 Selenium 做 UI 自动化,这套技术栈已经成了我们团队的核心生产力工具。它解决的不仅仅是“自动化”的问题,更是如何让自动化测试变得 可持续、易维护、好协作

简单来说,“Python 结合 pytest 进行 UI 自动化测试”就是用 Python 这门简洁高效的语言,驱动浏览器(通过 Selenium 等库)模拟用户操作,并利用 pytest 这个强大的测试框架来组织、运行和管理这些测试用例。它的核心价值在于: 用极低的代码量实现复杂的测试逻辑,用清晰的规则管理测试生命周期,用丰富的插件生态生成漂亮的报告和实现高级功能 。无论是测试一个简单的登录页面,还是一个拥有上百个功能模块的复杂 Web 应用,这套组合都能游刃有余。

适合谁来学?如果你是测试工程师,想从手工测试转向自动化;如果你是开发工程师,想为自己的项目补充前端自动化测试;甚至你是团队负责人,想引入一套标准化的自动化方案来提升交付质量,这篇文章都能给你一套从零搭建到实战优化的完整思路。接下来,我会带你深入这套组合的每一个核心环节,拆解其设计思想,并分享大量我在实际项目中踩过的坑和总结的技巧。

2. 核心框架设计与选型逻辑

在动手写代码之前,理清框架的设计思路至关重要。一个混乱的自动化项目,其维护成本很快就会超过它带来的收益。我们选择 Python + pytest + Selenium,背后有一整套经过实践检验的考量。

2.1 为什么是 Python?

首先,语言选型上,Python 几乎是自动化测试领域的“普通话”。其语法简洁,学习曲线平缓,能让测试人员快速上手,将精力集中在测试逻辑而非语言特性上。庞大的第三方库生态(PyPI)意味着你几乎能找到任何需要的工具,从 Web 驱动(Selenium)、API 测试(requests)、到移动端(Appium)、RPA(pyautogui)都能覆盖。这对于需要多技术栈融合的测试场景非常友好。此外,Python 与 DevOps 工具链(如 Jenkins, GitLab CI)的集成也异常顺畅。

2.2 为什么是 pytest 而非 unittest?

这是很多新手会困惑的点。Python 自带 unittest 模块,为何还要用 pytest?关键在于 “约定优于配置” “极强的扩展性”

  1. 更简洁的语法 :pytest 不需要你继承某个特定的类,任何以 test_ 开头的函数或方法都会被自动识别为测试用例。断言也直接用 Python 原生的 assert 语句,失败时 pytest 会给出极其详细的差异对比,这比 unittest 的 self.assertEqual() 直观太多。
  2. 强大的 Fixture 机制 :这是 pytest 的“灵魂”。Fixture 可以理解为测试的“脚手架”,用于提供测试所需的数据、环境或资源(如浏览器驱动实例),并能精确控制其生命周期(函数级、类级、模块级、会话级)。这完美解决了 UI 自动化中浏览器初始化、登录态管理、数据清理等繁琐问题。相比之下,unittest 的 setUp / tearDown 显得笨重且不够灵活。
  3. 丰富的插件生态 :你需要生成 HTML 报告?有 pytest-html 。需要更炫酷的 Allure 报告?有 pytest-allure 。需要控制用例执行顺序、分组、跳过、参数化?pytest 都原生支持或有成熟插件。这种“乐高积木”式的扩展能力,让框架能轻松适应不同项目的定制化需求。
  4. 优秀的命令行工具 :可以非常方便地选择运行某个目录、某个文件、某个类、甚至某个标记(mark)的用例,便于快速调试和分模块测试。

2.3 为什么结合 Selenium?

对于 Web UI 自动化,Selenium 是事实上的标准。它支持所有主流浏览器(Chrome, Firefox, Edge, Safari),提供了完整的浏览器操控 API。更重要的是,它的 WebDriver 协议是 W3C 标准,这意味着其生态稳定,社区活跃,遇到问题很容易找到解决方案。虽然也有 Playwright、Cypress 等后起之秀,但 Selenium 的普适性和成熟度,对于大多数企业级项目来说依然是稳妥的首选。

2.4 核心架构:Page Object Model (POM)

直接在被测页面上写定位和操作代码是自动化项目走向混乱的开端。我们必须采用 Page Object Model (页面对象模型) 设计模式。它的核心思想是将 页面元素定位 业务操作逻辑 分离,将 测试用例 页面对象 分离。

一个典型的三层 POM 架构如下:

  • 基础层 (BasePage) :封装所有 Selenium 的底层操作,如查找元素、点击、输入、等待等。所有页面类都继承自此基类。
  • 页面对象层 (Page Objects) :每个页面对应一个类。这个类里只包含该页面的元素定位符(如 username_input = (By.ID, ‘username’) )和在这个页面上的操作(如 login(username, password) )。 绝不包含断言
  • 测试用例层 (Test Cases) :这里才是真正的测试逻辑。调用页面对象的方法来完成业务流程,并在此处进行断言(验证结果)。

这样做的好处是:当页面 UI 发生变更时,你只需要去对应的页面对象类里修改元素定位符,所有引用该元素的测试用例都无需改动,极大提升了可维护性。测试用例读起来就像自然语言,清晰易懂。

实操心得 :在小型或中期项目中,我强烈建议将元素定位信息(如 id , xpath )从 Python 代码中剥离出来,存入 YAML JSON 文件。这样,即使不懂代码的团队成员(如产品、设计)也能在配置文件中维护定位符,实现更彻底的“测开分离”。上文示例中的 readelement.py click.yaml 就是这种思路的体现。

3. 环境搭建与核心工具链详解

光说不练假把式,我们一步步把环境搭起来。这里我会给出详细的步骤和每个步骤背后的原因。

3.1 Python 环境与项目管理

首先,不要使用系统自带的 Python。推荐使用 Miniconda pyenv 来创建独立的虚拟环境。这能避免项目间的包版本冲突。

# 1. 安装 Miniconda (略过)
# 2. 创建并激活一个专用于自动化测试的虚拟环境
conda create -n ui_auto python=3.8 -y
conda activate ui_auto

# 3. 在项目根目录初始化 pip 和创建 requirements.txt
pip install --upgrade pip

在你的项目根目录创建 requirements.txt 文件,这是管理依赖的标准做法:

# requirements.txt
selenium==4.15.0
pytest==7.4.3
pytest-html==4.0.2
pytest-xdist==3.5.0
allure-pytest==2.13.2
PyYAML==6.0.1
webdriver-manager==4.0.1

然后一键安装:

pip install -r requirements.txt
  • selenium : Web 自动化核心库。
  • pytest : 测试框架本体。
  • pytest-html : 生成简洁的 HTML 报告。
  • pytest-xdist : 实现测试用例并行执行,加速测试过程。
  • allure-pytest : 集成 Allure,生成非常强大和美观的交互式测试报告。
  • PyYAML : 用于读写 YAML 格式的配置文件(如元素定位文件)。
  • webdriver-manager : 强烈推荐! 它自动下载和管理浏览器驱动(ChromeDriver, GeckoDriver等),省去手动下载和配置 PATH 的麻烦。

3.2 浏览器驱动与 WebDriver Manager

过去,我们需要根据浏览器版本去官网下载对应版本的驱动,并设置系统路径。现在,用 webdriver-manager 可以完全自动化这个过程。

# 示例:使用 webdriver-manager 初始化 Chrome 驱动
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.options import Options

def get_chrome_driver():
    chrome_options = Options()
    # 常用配置
    chrome_options.add_argument('--start-maximized') # 启动最大化
    chrome_options.add_argument('--disable-gpu') # 禁用GPU加速,规避一些潜在问题
    chrome_options.add_argument('--no-sandbox') # 在CI环境(如Docker)中可能需要
    chrome_options.add_experimental_option('excludeSwitches', ['enable-logging']) # 禁用控制台无关日志

    # 关键!使用 webdriver-manager 自动获取驱动
    service = Service(ChromeDriverManager().install())
    driver = webdriver.Chrome(service=service, options=chrome_options)
    return driver

这样,无论团队成员或 CI 服务器上的 Chrome 版本如何,代码都能自动适配正确的驱动,极大降低了环境配置成本。

3.3 项目目录结构规划

一个清晰的目录结构是项目可维护性的基石。参考之前给出的示例,并结合最佳实践,我推荐如下结构:

ui_auto_project/
├── conftest.py           # pytest 全局配置,存放全局 fixture
├── pytest.ini           # pytest 配置文件
├── requirements.txt     # 项目依赖
├── main.py             # 测试执行入口(可选)
├── common/             # 通用模块
│   ├── __init__.py
│   ├── read_config.py  # 读取配置文件
│   └── readelement.py  # 读取元素定位文件
├── config/             # 配置目录
│   ├── __init__.py
│   ├── conf.py         # 配置常量(项目路径、定位模式映射等)
│   └── config.ini      # 配置文件(数据库、URL等)
├── page/               # 页面对象基类
│   ├── __init__.py
│   └── webpage.py      # 封装 Selenium 基础操作的 BasePage
├── page_element/       # 存放页面元素定位的 YAML 文件
│   ├── login_page.yaml
│   └── home_page.yaml
├── page_object/        # 具体的页面对象类
│   ├── __init__.py
│   ├── login_page.py
│   └── home_page.py
├── testcases/          # 测试用例层
│   ├── __init__.py
│   ├── test_login.py
│   └── test_order.py
├── reports/            # 测试报告输出目录
│   ├── html/
│   └── allure/
├── logs/               # 日志文件目录
├── utils/              # 工具函数
│   ├── __init__.py
│   ├── logger.py       # 日志模块
│   └── times.py        # 时间处理工具
└── data/               # 测试数据文件(如 JSON, CSV)
    └── test_data.json

这个结构体现了清晰的关注点分离,新人加入项目也能快速找到对应模块进行修改。

4. 核心代码实现与 POM 模式深度解析

现在,我们深入到每一层,看看代码具体怎么写。我会用登录功能作为贯穿始终的例子。

4.1 基础层 (BasePage) 封装

page/webpage.py 是我们的基石。它的目标是封装所有与 Selenium 直接交互的、不涉及具体业务的底层操作,并提供健壮的等待和异常处理。

# webpage.py
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, NoSuchElementException
from config.conf import cm  # 导入配置管理器
import logging

class WebPage:
    """Selenium 操作基类"""
    def __init__(self, driver):
        self.driver = driver
        self.logger = logging.getLogger(__name__)
        # 设置显式等待超时时间
        self.timeout = 20
        self.wait = WebDriverWait(self.driver, self.timeout)

    def find_element(self, locator):
        """
        查找单个元素(核心方法)
        :param locator: 元组,如 ('id', 'username') 或从YAML读取的 ('xpath', '//button')
        :return: WebElement 对象
        """
        try:
            # 使用显式等待,直到元素出现在DOM中
            element = self.wait.until(EC.presence_of_element_located(locator))
            self.logger.debug(f"成功定位元素: {locator}")
            return element
        except TimeoutException:
            self.logger.error(f"定位元素超时: {locator}")
            # 这里可以附加截图,方便调试
            self.save_screenshot(f"element_not_found_{locator[0]}_{locator[1]}")
            raise

    def find_elements(self, locator):
        """查找多个元素"""
        try:
            elements = self.wait.until(EC.presence_of_all_elements_located(locator))
            return elements
        except TimeoutException:
            self.logger.warning(f"定位一组元素未找到: {locator}")
            return [] # 返回空列表,避免用例因找不到元素而中断,具体看业务场景

    def click(self, locator):
        """点击元素"""
        element = self.find_element(locator)
        try:
            # 先尝试等待元素可点击
            clickable_element = WebDriverWait(self.driver, 10).until(
                EC.element_to_be_clickable(locator)
            )
            clickable_element.click()
            self.logger.info(f"点击元素: {locator}")
        except Exception as e:
            self.logger.error(f"点击元素失败 {locator}: {e}")
            raise

    def input_text(self, locator, text):
        """输入文本,输入前先清空"""
        element = self.find_element(locator)
        element.clear()
        element.send_keys(text)
        self.logger.info(f"在元素 {locator} 中输入文本: {text}")

    def get_text(self, locator):
        """获取元素文本"""
        element = self.find_element(locator)
        return element.text

    def save_screenshot(self, name):
        """保存截图,用于失败调试"""
        import datetime
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
        filepath = f"./screenshots/{name}_{timestamp}.png"
        self.driver.save_screenshot(filepath)
        self.logger.info(f"截图已保存至: {filepath}")
        return filepath

    # ... 其他通用方法,如切换窗口/iframe、执行JS等

注意事项 find_element 方法中我使用了 EC.presence_of_element_located ,它只要求元素存在于 DOM 中,不一定可见或可点击。对于点击操作,在 click 方法里我额外使用了 EC.element_to_be_clickable 等待,这更符合实际交互逻辑,能有效规避因元素未完全渲染导致的点击失败。

4.2 元素定位与数据驱动

将元素定位信息从代码中分离是专业化的标志。我们使用 YAML 文件。

首先,在 config/conf.py 中定义定位模式映射和路径:

# conf.py
import os
from selenium.webdriver.common.by import By

class ConfigManager:
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    ELEMENT_PATH = os.path.join(BASE_DIR, 'page_element')

    LOCATE_MODE = {
        'id': By.ID,
        'name': By.NAME,
        'class': By.CLASS_NAME,
        'tag': By.TAG_NAME,
        'link': By.LINK_TEXT,
        'partial_link': By.PARTIAL_LINK_TEXT,
        'xpath': By.XPATH,
        'css': By.CSS_SELECTOR
    }

cm = ConfigManager()

然后,创建 common/readelement.py 来读取 YAML:

# readelement.py
import os
import yaml
from config.conf import cm

class Element:
    def __init__(self, page_name):
        self.file_name = f'{page_name}.yaml'
        self.element_path = os.path.join(cm.ELEMENT_PATH, self.file_name)
        if not os.path.exists(self.element_path):
            raise FileNotFoundError(f"元素文件 {self.file_name} 不存在!")
        with open(self.element_path, 'r', encoding='utf-8') as f:
            self.data = yaml.safe_load(f)

    def __getitem__(self, key):
        """通过键名获取定位符元组,如 login['username_input']"""
        locator_str = self.data.get(key)
        if not locator_str:
            raise KeyError(f"在文件 {self.file_name} 中未找到键: {key}")
        # 假设YAML中格式为: `username_input: "id==username"`
        locate_method, value = locator_str.split('==')
        if locate_method not in cm.LOCATE_MODE:
            raise ValueError(f"不支持的定位方式: {locate_method}")
        return (cm.LOCATE_MODE[locate_method], value)

接着,在 page_element/login_page.yaml 中定义元素:

# login_page.yaml
username_input: "id==username"
password_input: "name==password"
login_button: "css==button[type='submit']"
error_message: "class==alert-error"

4.3 页面对象层实现

现在,创建具体的页面对象类 page_object/login_page.py

# login_page.py
from page.webpage import WebPage
from common.readelement import Element
import allure

class LoginPage(WebPage):
    def __init__(self, driver):
        super().__init__(driver)
        self.element = Element('login_page') # 加载 login_page.yaml

    @allure.step("输入用户名")
    def input_username(self, username):
        self.input_text(self.element['username_input'], username)

    @allure.step("输入密码")
    def input_password(self, password):
        self.input_text(self.element['password_input'], password)

    @allure.step("点击登录按钮")
    def click_login_button(self):
        self.click(self.element['login_button'])

    @allure.step("执行登录流程")
    def login(self, username, password):
        self.input_username(username)
        self.input_password(password)
        self.click_login_button()

    @allure.step("获取错误提示信息")
    def get_error_message(self):
        return self.get_text(self.element['error_message'])

注意 @allure.step 装饰器,它会在 Allure 报告中为每个步骤生成一个可折叠的节点,让报告更清晰。

4.4 测试用例层与 pytest Fixture

测试用例应该只关心业务流和断言。所有环境准备和清理工作交给 conftest.py 中的 fixture。

首先,创建全局的 conftest.py

# conftest.py
import pytest
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options

@pytest.fixture(scope="session")
def driver():
    """会话级别的 fixture,整个测试会话只启动一次浏览器"""
    chrome_options = Options()
    chrome_options.add_argument('--start-maximized')
    chrome_options.add_argument('--disable-gpu')
    # 无头模式选项,用于CI环境
    # if os.getenv('RUN_IN_CI'):
    #     chrome_options.add_argument('--headless')
    #     chrome_options.add_argument('--no-sandbox')
    #     chrome_options.add_argument('--disable-dev-shm-usage')

    service = Service(ChromeDriverManager().install())
    _driver = webdriver.Chrome(service=service, options=chrome_options)
    _driver.implicitly_wait(10) # 设置全局隐式等待

    yield _driver # 测试用例执行时使用这个 driver 实例

    # 所有测试结束后执行清理
    _driver.quit()
    print("测试结束,浏览器已关闭。")

@pytest.fixture(scope="function")
def login_page(driver):
    """函数级别的 fixture,每个测试函数都会得到一个全新的页面对象实例"""
    from page_object.login_page import LoginPage
    page = LoginPage(driver)
    page.driver.get("https://your-test-app.com/login") # 打开登录页
    yield page
    # 每个用例后可以清理cookie或刷新页面,保持独立
    # driver.delete_all_cookies()

然后,编写测试用例 testcases/test_login.py

# test_login.py
import allure
import pytest
from page_object.login_page import LoginPage

class TestLogin:
    """登录功能测试"""

    @allure.feature("登录功能")
    @allure.story("成功登录")
    def test_login_success(self, login_page):
        """测试使用正确用户名密码登录成功"""
        login_page.login(username="correct_user", password="correct_pass")
        # 断言:登录后应跳转到首页,通过URL或页面特定元素判断
        assert login_page.driver.current_url == "https://your-test-app.com/home"
        # 或者断言首页的欢迎语出现
        # assert login_page.get_text(welcome_locator) == "Welcome, correct_user!"

    @allure.feature("登录功能")
    @allure.story("登录失败-用户名错误")
    @pytest.mark.parametrize("username, password, expected_error", [
        ("wrong_user", "correct_pass", "用户名或密码错误"),
        ("", "correct_pass", "用户名不能为空"),
        ("correct_user", "", "密码不能为空"),
    ])
    def test_login_failure(self, login_page, username, password, expected_error):
        """参数化测试多种登录失败场景"""
        login_page.login(username=username, password=password)
        # 断言错误信息符合预期
        actual_error = login_page.get_error_message()
        assert expected_error in actual_error, f"期望错误信息包含'{expected_error}',实际为'{actual_error}'"

这里展示了 pytest 强大的参数化功能 ( @pytest.mark.parametrize ),可以用一组数据驱动多个测试场景,避免写重复代码。

5. 测试执行、报告生成与高级技巧

框架搭好了,用例写好了,接下来就是如何高效地运行它们并得到直观的反馈。

5.1 使用 pytest 命令行执行测试

pytest 提供了极其灵活的执行方式。

# 1. 运行所有测试
pytest

# 2. 运行指定目录下的测试
pytest testcases/

# 3. 运行指定文件中的测试
pytest testcases/test_login.py

# 4. 运行指定类或方法
pytest testcases/test_login.py::TestLogin
pytest testcases/test_login.py::TestLogin::test_login_success

# 5. 使用标记 (mark) 运行特定测试
# 先在用例上打标记:@pytest.mark.smoke
pytest -m smoke

# 6. 并行运行测试,加速执行 (需要 pytest-xdist)
pytest -n auto  # auto 表示使用与CPU核心数相同的worker

# 7. 生成简易HTML报告 (需要 pytest-html)
pytest --html=reports/html/report.html --self-contained-html

# 8. 生成 Allure 结果数据
pytest --alluredir=reports/allure-results

5.2 生成 Allure 测试报告

Allure 报告是提升测试结果可读性和团队协作效率的神器。生成报告需要两步:

  1. 运行测试并生成结果数据 :如上所述,使用 --alluredir 参数。
  2. 将结果数据生成为 HTML 报告
    # 方式一:使用命令行(需先安装Allure命令行工具)
    allure generate reports/allure-results -o reports/allure-report --clean
    allure open reports/allure-report  # 在浏览器中打开报告
    
    # 方式二:在Python代码中调用(推荐,便于集成)
    import os
    os.system('allure generate reports/allure-results -o reports/allure-report --clean')
    

Allure 报告会展示清晰的测试套件结构、通过/失败/跳过用例的统计、详细的步骤日志、截图、以及历史趋势图,非常专业。

5.3 常见问题与排查技巧实录

在实际项目中,你会遇到各种各样的问题。这里记录一些高频问题和我的解决思路。

问题1:元素定位不到,报 NoSuchElementException TimeoutException

  • 可能原因及排查
    1. 等待时间不足 :UI 加载需要时间。优先使用显式等待 ( WebDriverWait ),而非硬性等待 ( time.sleep ) 或仅依赖隐式等待。
    2. 定位符错误或已变更 :使用浏览器开发者工具(F12)的 Console 选项卡,输入 $$(“你的css选择器”) $x(“你的xpath”) 验证是否能找到元素。 XPath 尽量使用相对路径,避免使用绝对路径和依赖不稳定的属性(如自动生成的id)。
    3. 元素在 iframe 或 shadow DOM 中 :需要先切换到对应的 iframe ( driver.switch_to.frame ) 或使用 shadow_root 属性访问。
    4. 页面未加载完全 :在操作前等待某个关键元素出现,作为页面加载完成的标志。
    5. 浏览器窗口未最大化 :某些响应式页面元素在窗口较小时可能被隐藏或布局改变。在 fixture 中设置 driver.maximize_window()

问题2:测试在本地通过,但在 CI(如 Jenkins)上失败。

  • 可能原因及排查
    1. 无头模式 (Headless) 差异 :CI 环境通常以无头模式运行浏览器。一些动画或懒加载在无头模式下行为可能不同。在 Chrome 无头模式选项中添加 --window-size=1920,1080 并确保使用了合适的等待。
    2. 环境隔离 :CI 环境是干净的,可能缺少 cookies、localStorage 等登录态。需要在测试开始前通过 API 或 UI 完成登录,并将登录态(cookies)保存和复用。上文 conftest.py 示例中向浏览器添加 cookies 就是解决此问题。
    3. 资源竞争与并发 :如果并行执行测试,确保测试用例之间是隔离的(使用独立的用户账号、测试数据),避免因共享数据导致冲突。使用 pytest-xdist --dist=loadscope 参数可以尝试将同一个模块的测试分配给同一个 worker,减少资源竞争。

问题3:测试执行速度慢。

  • 优化策略
    1. 并行执行 :使用 pytest-xdist
    2. 优化等待策略 :减少硬性等待 ( time.sleep ),多用显式等待,并设置合理的超时时间。
    3. 复用浏览器会话 :使用 scope=’session’ 的 fixture 来初始化一次浏览器,所有测试共用。但要注意测试间的清理(如登出、清理 cookies),防止状态污染。
    4. 启用缓存 :对于从网络下载的静态资源,可以配置浏览器使用缓存。
    5. 选择更快的定位器 :通常 id > name > css > xpath xpath 在复杂文档中遍历可能较慢。

问题4:测试报告中没有截图或截图不清晰。

  • 解决方案 :在 conftest.py 中编写一个自动截图 fixture,并将其与 pytest 的钩子函数结合。
    # conftest.py
    import pytest
    from selenium import webdriver
    
    @pytest.hookimpl(tryfirst=True, hookwrapper=True)
    def pytest_runtest_makereport(item, call):
        """
        获取每个测试用例执行结果的钩子函数
        """
        outcome = yield
        rep = outcome.get_result()
        # 只关注用例执行阶段(setup, call, teardown)中的call,即测试步骤本身
        if rep.when == "call" and rep.failed:
            # 获取测试用例中的driver fixture
            for name, fixtureinfo in item._fixtureinfo.name2fixturedefs.items():
                if name == "driver":
                    driver = item.funcargs[name]
                    # 调用之前封装好的截图方法
                    screenshot_path = driver.save_screenshot(f"failure_{item.name}")
                    # 将截图附件添加到Allure报告(如果使用Allure)
                    if hasattr(rep, 'extra'):
                        import allure
                        allure.attach.file(screenshot_path, name="失败截图", attachment_type=allure.attachment_type.PNG)
                    break
    
    这样,每当测试失败时,会自动截取当前浏览器页面并附加到报告中。

问题5:如何管理不同的测试环境(测试/预发/生产)?

  • 最佳实践 :使用配置文件和环境变量。在 config.ini config.yaml 中定义不同环境的 URL、数据库连接等信息。通过环境变量(如 ENV=testing )决定加载哪套配置。
    # config/conf.py
    import os
    from configparser import ConfigParser
    
    env = os.getenv('ENV', 'testing').upper() # 默认为测试环境
    config = ConfigParser()
    config.read('config/config.ini')
    
    BASE_URL = config.get(env, 'base_url')
    DB_HOST = config.get(env, 'db_host')
    

坚持记录这些问题的解决方案,并形成团队的“知识库”,能极大提升后续的排查效率。UI 自动化测试是一个需要持续投入和优化的工程,初期搭建框架会花些时间,但一旦体系建立起来,它所带来的回归测试效率提升和产品质量保障将是巨大的。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐