Pytest+Selenium搭建自动化框架-保姆级实战教程

从零开始,手把手带你搭建一套企业级UI自动化测试框架。从环境安装到框架设计,从编写第一条用例到生成Allure测试报告,每一步都有截图和代码,保证你能跑通。


一、阅读前:为什么选Pytest+Selenium

对比维度 Pytest+Selenium unittest+Selenium Robot Framework
上手难度 ★★☆ 中等 ★★★ 偏高 ★☆☆ 低(但学习曲线陡)
扩展性 ★★★ 极强(插件生态) ★★☆ 一般 ★★☆ 受限于框架
社区活跃度 ★★★ 最活跃 ★★☆ 经典但少更新 ★☆☆ 用户较少
报告美观度 ★★★ Allure报告 ★☆☆ HTMLTestRunner ★★☆ 自带报告
适合场景 企业级项目 简单项目 不写代码的测试

一句话总结:Pytest+Selenium 是当前市场上 UI 自动化最主流的技术组合,面试必问、工作必用。


二、环境搭建:从零到运行第一条用例

2.1 Python环境准备

bash

复制代码
# 确认Python版本(建议3.8+)
python --version

# 未安装Python?去官网下载:https://www.python.org/downloads/
# 安装时务必勾选 "Add Python to PATH"

2.2 创建项目目录结构

bash

复制代码
# 工作目录中执行,创建如下结构
mkdir selenium_pytest_demo
cd selenium_pytest_demo

# 推荐目录结构
selenium_pytest_demo/
├── config/             # 配置文件
│   └── config.yaml
├── page/               # Page Object页面层
│   ├── base_page.py
│   ├── login_page.py
│   └── home_page.py
├── testcases/          # 测试用例
│   ├── conftest.py
│   ├── test_login.py
│   └── test_search.py
├── data/               # 测试数据
│   └── test_data.yaml
├── utils/              # 工具类
│   ├── driver_util.py
│   └── log_util.py
├── reports/            # 测试报告
│   └── allure-results/
├── pytest.ini          # Pytest配置
└── requirements.txt    # 依赖清单

2.3 安装依赖

bash

复制代码
# 创建requirements.txt
pip install selenium          # Web自动化核心
pip install pytest            # 测试框架
pip install pytest-xdist      # 并行执行
pip install pytest-rerunfailures  # 失败重跑
pip install allure-pytest     # Allure报告
pip install pyyaml            # YAML配置文件解析
pip install webdriver-manager # 自动管理浏览器驱动

或者一次性安装:

bash

复制代码
# requirements.txt内容
selenium>=4.15.0
pytest>=7.4.0
pytest-xdist>=3.5.0
pytest-rerunfailures>=13.0
allure-pytest>=2.13.0
pyyaml>=6.0
webdriver-manager>=4.0.0

# 执行安装
pip install -r requirements.txt

⚠️ 新手常见坑 :selenium 4.x 之后不再需要手动下载 chromedriver,webdriver-manager 会自动帮你搞定!


三、核心代码:一步步搭建

3.1 浏览器驱动管理(driver_util.py)

python

复制代码
# utils/driver_util.py
"""
浏览器驱动管理工具
自动下载匹配版本的浏览器驱动,支持Chrome、Firefox、Edge
"""
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager


class DriverManager:
    """浏览器驱动管理器"""
    
    # 支持浏览器类型映射
    BROWSER_MAP = {
        'chrome': (webdriver.Chrome, ChromeDriverManager),
        'firefox': (webdriver.Firefox, GeckoDriverManager),
        'edge': (webdriver.Edge, EdgeChromiumDriverManager),
    }
    
    @staticmethod
    def get_driver(browser='chrome', headless=False):
        """
        获取浏览器驱动实例
        
        Args:
            browser: 浏览器类型 chrome/firefox/edge
            headless: 是否无头模式(不显示浏览器界面)
        
        Returns:
            WebDriver实例
        """
        if browser not in DriverManager.BROWSER_MAP:
            raise ValueError(f"不支持的浏览器类型: {browser},可选: {list(DriverManager.BROWSER_MAP.keys())}")
        
        driver_class, manager_class = DriverManager.BROWSER_MAP[browser]
        
        # 自动下载匹配的浏览器驱动
        driver_path = manager_class().install()
        service = Service(driver_path)
        
        # 浏览器选项配置
        options = webdriver.ChromeOptions() if browser == 'chrome' else \
                  webdriver.FirefoxOptions() if browser == 'firefox' else \
                  webdriver.EdgeOptions()
        
        # 反自动化检测(防止网站识别为自动化脚本)
        options.add_argument('--disable-blink-features=AutomationControlled')
        options.add_experimental_option('excludeSwitches', ['enable-automation'])
        options.add_experimental_option('useAutomationExtension', False)
        
        # 无头模式配置
        if headless:
            options.add_argument('--headless=new')  # Chrome 112+ 推荐写法
            options.add_argument('--disable-gpu')
            options.add_argument('--no-sandbox')
            options.add_argument('--window-size=1920,1080')
        
        # 窗口最大化
        options.add_argument('--start-maximized')
        
        # 忽略证书错误(测试环境常见问题)
        options.add_argument('--ignore-certificate-errors')
        
        # 创建并返回驱动
        driver = driver_class(service=service, options=options)
        driver.implicitly_wait(10)  # 全局隐式等待10秒
        
        return driver


if __name__ == '__main__':
    # 快速测试:启动浏览器并打开百度
    drv = DriverManager.get_driver()
    drv.get('https://www.baidu.com')
    print(f"当前页面标题: {drv.title}")
    drv.quit()

3.2 Page Object 基类(base_page.py)

python

复制代码
# page/base_page.py
"""
Page Object 模式基类
封装所有页面共用的操作方法,子页面继承即可
"""
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.common.exceptions import TimeoutException, NoSuchElementException
import logging

logger = logging.getLogger(__name__)


class BasePage:
    """页面基类,封装通用操作"""
    
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, timeout=15, poll_frequency=0.5)
    
    # ==================== 元素定位封装 ====================
    
    def find_element(self, locator):
        """
        查找单个元素
        
        Args:
            locator: (By.XXX, 'value') 元组
        
        Returns:
            WebElement对象
        
        Example:
            find_element((By.ID, 'username'))
        """
        try:
            return self.wait.until(EC.presence_of_element_located(locator))
        except TimeoutException:
            logger.error(f"元素定位超时: {locator}")
            raise
    
    def find_elements(self, locator):
        """查找多个元素"""
        try:
            return self.wait.until(EC.presence_of_all_elements_located(locator))
        except TimeoutException:
            logger.error(f"元素列表定位超时: {locator}")
            return []
    
    # ==================== 点击操作封装 ====================
    
    def click(self, locator):
        """等待元素可点击后点击"""
        element = self.wait.until(EC.element_to_be_clickable(locator))
        element.click()
        logger.info(f"点击元素: {locator}")
    
    def js_click(self, locator):
        """通过JavaScript点击(适用于普通点击失效的情况)"""
        element = self.find_element(locator)
        self.driver.execute_script("arguments[0].click();", element)
        logger.info(f"JS点击元素: {locator}")
    
    # ==================== 输入操作封装 ====================
    
    def input_text(self, locator, text, clear_first=True):
        """
        输入文本
        
        Args:
            locator: 元素定位器
            text: 输入的文本
            clear_first: 是否先清空
        """
        element = self.find_element(locator)
        if clear_first:
            element.clear()
        element.send_keys(text)
        logger.info(f"输入文本到 {locator}: {text}")
    
    # ==================== 获取信息 ====================
    
    def get_text(self, locator):
        """获取元素文本"""
        return self.find_element(locator).text
    
    def get_attribute(self, locator, attr_name):
        """获取元素属性值"""
        return self.find_element(locator).get_attribute(attr_name)
    
    # ==================== 断言辅助 ====================
    
    def is_element_present(self, locator, timeout=5):
        """判断元素是否存在于DOM"""
        try:
            WebDriverWait(self.driver, timeout).until(
                EC.presence_of_element_located(locator)
            )
            return True
        except TimeoutException:
            return False
    
    def is_element_visible(self, locator, timeout=5):
        """判断元素是否可见"""
        try:
            WebDriverWait(self.driver, timeout).until(
                EC.visibility_of_element_located(locator)
            )
            return True
        except TimeoutException:
            return False
    
    # ==================== 页面操作 ====================
    
    def get_title(self):
        """获取当前页面标题"""
        return self.driver.title
    
    def get_current_url(self):
        """获取当前URL"""
        return self.driver.current_url
    
    def switch_to_new_window(self):
        """切换到最新打开的窗口"""
        handles = self.driver.window_handles
        self.driver.switch_to.window(handles[-1])
        logger.info(f"切换到新窗口: {self.driver.title}")
    
    def scroll_to_element(self, locator):
        """滚动到元素可见位置"""
        element = self.find_element(locator)
        self.driver.execute_script("arguments[0].scrollIntoView(true);", element)
    
    def take_screenshot(self, filename=None):
        """截图"""
        import os
        from datetime import datetime
        if filename is None:
            filename = f"screenshot_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
        filepath = os.path.join('reports', 'screenshots', filename)
        os.makedirs(os.path.dirname(filepath), exist_ok=True)
        self.driver.save_screenshot(filepath)
        logger.info(f"截图已保存: {filepath}")
        return filepath

3.3 业务页面对象(login_page.py)

python

复制代码
# page/login_page.py
"""
登录页面对象
以百度登录页为例
"""
from selenium.webdriver.common.by import By
from page.base_page import BasePage


class LoginPage(BasePage):
    """百度登录页面"""
    
    # ==================== 元素定位(页面对象的核心) ====================
    
    # 首页的"登录"按钮
    LOGIN_BTN = (By.LINK_TEXT, "登录")
    
    # 用户名登录方式的切换按钮
    USERNAME_LOGIN_TAB = (By.ID, "TANGRAM__PSP_11__changePwdCodeItem")
    
    # 用户名输入框
    USERNAME_INPUT = (By.ID, "TANGRAM__PSP_11__userName")
    
    # 密码输入框
    PASSWORD_INPUT = (By.ID, "TANGRAM__PSP_11__password")
    
    # 登录提交按钮
    SUBMIT_BTN = (By.ID, "TANGRAM__PSP_11__submit")
    
    # 登录错误提示
    ERROR_MSG = (By.CLASS_NAME, "error-text")
    
    # ==================== 页面操作方法 ====================
    
    def goto_login(self):
        """进入登录页面"""
        self.driver.get("https://www.baidu.com")
        self.click(self.LOGIN_BTN)
    
    def switch_to_username_login(self):
        """切换到用户名密码登录方式"""
        self.click(self.USERNAME_LOGIN_TAB)
    
    def login(self, username, password):
        """
        执行登录操作
        
        Args:
            username: 用户名
            password: 密码
        """
        self.input_text(self.USERNAME_INPUT, username)
        self.input_text(self.PASSWORD_INPUT, password)
        self.click(self.SUBMIT_BTN)
    
    def get_error_message(self):
        """获取登录错误提示信息"""
        if self.is_element_visible(self.ERROR_MSG, timeout=3):
            return self.get_text(self.ERROR_MSG)
        return None
    
    def login_and_get_result(self, username, password):
        """
        完整登录流程并返回结果
        
        Returns:
            tuple: (是否成功, 错误信息)
        """
        self.goto_login()
        self.switch_to_username_login()
        self.login(username, password)
        
        # 判断是否登录成功(跳转到首页即为成功)
        import time
        time.sleep(2)
        if "百度首页" in self.get_title() or "baidu" in self.get_current_url():
            return True, None
        else:
            return False, self.get_error_message()

3.4 conftest.py(Pytest夹具配置)

python

复制代码
# testcases/conftest.py
"""
Pytest全局配置和夹具
conftest.py是Pytest特有的配置文件,同级目录及子目录的用例自动生效
"""
import pytest
import os
import allure
from utils.driver_util import DriverManager


@pytest.fixture(scope='session')
def browser_type():
    """
    浏览器类型配置
    优先级:命令行参数 > 环境变量 > 默认值(chrome)
    """
    return os.environ.get('TEST_BROWSER', 'chrome')


@pytest.fixture(scope='function')
def driver(browser_type):
    """
    浏览器驱动夹具
    scope='function' 表示每个测试用例都会创建新的浏览器实例
    
    yield 后面的代码会在测试用例结束后自动执行
    """
    # 测试前:创建浏览器驱动
    drv = DriverManager.get_driver(browser=browser_type)
    drv.maximize_window()
    allure.attach(browser_type, '浏览器类型', allure.attachment_type.TEXT)
    
    yield drv  # 将driver传递给测试用例
    
    # 测试后:关闭浏览器(无论用例成功还是失败都会执行)
    drv.quit()


@pytest.fixture(scope='function')
def driver_headless(browser_type):
    """
    无头浏览器夹具
    适用于CI/CD环境或不需要查看界面的场景
    """
    drv = DriverManager.get_driver(browser=browser_type, headless=True)
    yield drv
    drv.quit()


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """
    用例失败时自动截图
    Pytest Hook钩子函数,每个用例执行后触发
    """
    outcome = yield
    report = outcome.get_result()
    
    # 仅处理测试执行阶段且失败的用例
    if report.when == 'call' and report.failed:
        driver_fixture = item.funcargs.get('driver')
        if driver_fixture:
            # 截图并附加到Allure报告
            screenshot = driver_fixture.get_screenshot_as_png()
            allure.attach(
                screenshot,
                name=f"失败截图_{item.name}",
                attachment_type=allure.attachment_type.PNG
            )


def pytest_addoption(parser):
    """添加自定义命令行参数"""
    parser.addoption(
        '--browser',
        action='store',
        default='chrome',
        help='指定浏览器类型: chrome, firefox, edge'
    )
    parser.addoption(
        '--headless',
        action='store_true',
        default=False,
        help='是否使用无头模式'
    )

3.5 测试用例编写(test_login.py)

python

复制代码
# testcases/test_login.py
"""
登录功能测试用例
用例编号规则:TPL-模块-编号
"""
import pytest
import allure
from page.login_page import LoginPage


@allure.feature('登录功能')
class TestLogin:
    """登录功能测试套件"""
    
    @allure.story('正常登录')
    @allure.title('TC-LOGIN-001:使用正确的用户名和密码登录')
    @allure.description('验证使用有效凭据可以成功登录系统')
    @allure.severity(allure.severity_level.CRITICAL)
    def test_login_success(self, driver):
        """正常登录用例"""
        login_page = LoginPage(driver)
        
        with allure.step('1. 进入百度首页并打开登录页面'):
            login_page.goto_login()
        
        with allure.step('2. 切换到用户名密码登录方式'):
            login_page.switch_to_username_login()
        
        with allure.step('3. 输入正确的用户名和密码'):
            login_page.login('your_username', 'your_password')
        
        with allure.step('4. 验证登录成功'):
            # 此处为示例断言,实际测试应使用真实账号
            # 验证页面跳转或出现用户昵称
            assert '百度' in login_page.get_title(), "登录后页面标题应包含'百度'"
    
    @allure.story('异常登录')
    @allure.title('TC-LOGIN-002:使用错误的密码登录')
    @allure.description('验证使用无效凭据时系统给出明确错误提示')
    @allure.severity(allure.severity_level.NORMAL)
    def test_login_wrong_password(self, driver):
        """错误密码登录用例"""
        login_page = LoginPage(driver)
        
        with allure.step('1. 进入登录页面'):
            login_page.goto_login()
        
        with allure.step('2. 切换到用户名密码登录'):
            login_page.switch_to_username_login()
        
        with allure.step('3. 输入用户名和错误密码'):
            login_page.login('test@qq.com', 'wrong_password_123')
        
        with allure.step('4. 验证出现错误提示'):
            error = login_page.get_error_message()
            assert error is not None, "应出现密码错误提示"
            allure.attach(error, '错误提示信息', allure.attachment_type.TEXT)
    
    @allure.story('异常登录')
    @allure.title('TC-LOGIN-003:用户名为空登录')
    @allure.description('验证用户名为空时的提示')
    @allure.severity(allure.severity_level.NORMAL)
    @pytest.mark.parametrize('username,password,expected', [
        ('', '123456', '请输入用户名'),
        ('test@qq.com', '', '请输入密码'),
        ('', '', '请输入用户名'),
    ])
    def test_login_empty_fields(self, driver, username, password, expected):
        """
        数据驱动测试:空值校验
        一条用例自动生成3组测试数据执行
        
        参数化是Pytest的核心特性,大幅减少重复代码
        """
        login_page = LoginPage(driver)
        login_page.goto_login()
        login_page.switch_to_username_login()
        login_page.login(username, password)
        
        error = login_page.get_error_message()
        assert error is not None, f"当用户名为'{username}'、密码为'{password}'时应有提示"

3.6 pytest.ini配置

ini

复制代码
# pytest.ini
[pytest]
# 测试用例路径
testpaths = testcases

# 命令行默认参数
addopts = 
    -v                          # verbose详细输出
    -s                          # 不捕获print输出
    --strict-markers            # 严格标记模式
    --tb=short                  # 简短的错误追踪
    --alluredir=reports/allure-results  # Allure报告输出目录
    --maxfail=3                 # 失败3个用例后停止

# 自定义标记(用于用例分类和执行筛选)
markers =
    smoke: 冒烟测试用例
    regression: 回归测试用例
    slow: 执行较慢的测试
    login: 登录相关用例
    search: 搜索相关用例

# 日志配置
log_cli = true
log_cli_level = INFO
log_cli_format = %(asctime)s [%(levelname)s] %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S

四、运行测试

4.1 基础运行命令

bash

复制代码
# 运行全部用例
pytest

# 运行指定文件
pytest testcases/test_login.py

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

# 运行带特定标记的用例
pytest -m smoke          # 只跑冒烟测试
pytest -m "login and smoke"  # 同时满足两个标记

# 运行并输出详细日志
pytest -v -s

4.2 高级运行模式

bash

复制代码
# 并行执行(2个进程同时跑,大幅缩短总耗时)
pytest -n 2

# 自动根据CPU核心数并行
pytest -n auto

# 失败重跑(失败用例自动重试2次)
pytest --reruns 2

# 失败重跑+间隔等待
pytest --reruns 2 --reruns-delay 5

# 只运行上次失败的用例
pytest --lf  # --last-failed

# 先跑失败的,再跑其他的
pytest --ff  # --failed-first

# 按用例名关键词筛选
pytest -k "login"       # 跑所有包含login的用例
pytest -k "login and not slow"  # 跑login但不跑slow的

4.3 指定浏览器运行

bash

复制代码
# 用Firefox跑
pytest --browser=firefox

# 用Edge跑
pytest --browser=edge

# 无头模式(不弹浏览器窗口,适合CI环境)
pytest --headless

五、生成Allure测试报告

5.1 安装Allure

bash

复制代码
# Windows (推荐用Scoop)
scoop install allure

# 或手动下载:https://github.com/allure-framework/allure2/releases
# 解压后将bin目录添加到PATH环境变量

# Mac
brew install allure

# 验证安装
allure --version

5.2 生成并查看报告

bash

复制代码
# 第1步:运行测试(同时生成报告数据)
pytest --alluredir=reports/allure-results

# 第2步:启动报告服务器查看
allure serve reports/allure-results

# 或生成本地静态报告
allure generate reports/allure-results -o reports/allure-report --clean
allure open reports/allure-report

5.3 Allure常用装饰器

python

复制代码
import allure

# 功能模块
@allure.feature('用户管理')

# 用户故事
@allure.story('用户注册')

# 用例标题(报告中的展示名)
@allure.title('TC-001:注册成功')

# 用例描述
@allure.description('验证使用合法信息注册用户成功')

# 严重级别
@allure.severity(allure.severity_level.BLOCKER)   # 阻断
@allure.severity(allure.severity_level.CRITICAL)  # 严重
@allure.severity(allure.severity_level.NORMAL)    # 一般
@allure.severity(allure.severity_level.MINOR)     # 轻微

# 测试步骤(报告中可视化展示每步)
with allure.step('1. 打开登录页面'):
    page.open()

# 附加信息
allure.attach('文本内容', '附件名', allure.attachment_type.TEXT)
allure.attach(driver.get_screenshot_as_png(), '截图', allure.attachment_type.PNG)

六、进阶:框架优化实战

6.1 YAML数据驱动

python

复制代码
# data/test_data.yaml
login_cases:
  - case_id: 'TC-LOGIN-001'
    title: '正确账号密码登录'
    username: 'admin'
    password: '123456'
    expected: 'success'
    tags: ['smoke', 'p0']
  
  - case_id: 'TC-LOGIN-002'
    title: '错误密码登录'
    username: 'admin'
    password: 'wrong'
    expected: '密码错误'
    tags: ['p1']
  
  - case_id: 'TC-LOGIN-003'
    title: '空用户名登录'
    username: ''
    password: '123456'
    expected: '请输入用户名'
    tags: ['p1']

python

复制代码
# 在testcases中使用YAML数据
import yaml
import pytest

def load_test_data(file_path='data/test_data.yaml'):
    """从YAML文件加载测试数据"""
    with open(file_path, 'r', encoding='utf-8') as f:
        return yaml.safe_load(f)

@pytest.mark.parametrize(
    'case',
    load_test_data()['login_cases'],
    ids=lambda case: case['case_id']
)
def test_login_with_yaml_data(driver, case):
    """使用YAML数据驱动的登录测试"""
    login_page = LoginPage(driver)
    login_page.goto_login()
    login_page.switch_to_username_login()
    login_page.login(case['username'], case['password'])
    
    error = login_page.get_error_message()
    if case['expected'] == 'success':
        assert '百度' in login_page.get_title()
    else:
        assert case['expected'] in (error or '')

6.2 日志配置

python

复制代码
# utils/log_util.py
import logging
import os
from datetime import datetime

def setup_logger(name='auto_test', log_dir='reports/logs'):
    """
    配置日志器
    同时输出到控制台和文件,按日期滚动
    """
    os.makedirs(log_dir, exist_ok=True)
    
    logger = logging.getLogger(name)
    logger.setLevel(logging.DEBUG)
    
    # 避免重复添加handler
    if logger.handlers:
        return logger
    
    # 日志格式
    formatter = logging.Formatter(
        '[%(asctime)s] [%(levelname)s] [%(filename)s:%(lineno)d] - %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S'
    )
    
    # 控制台输出
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_handler.setFormatter(formatter)
    
    # 文件输出(按日期命名)
    log_file = os.path.join(
        log_dir,
        f"test_{datetime.now().strftime('%Y%m%d')}.log"
    )
    file_handler = logging.FileHandler(log_file, encoding='utf-8')
    file_handler.setLevel(logging.DEBUG)
    file_handler.setFormatter(formatter)
    
    logger.addHandler(console_handler)
    logger.addHandler(file_handler)
    
    return logger

# 全局使用
logger = setup_logger()

6.3 Jenkins集成配置

groovy

复制代码
// Jenkinsfile
pipeline {
    agent any
    
    // 环境变量
    environment {
        TEST_BROWSER = 'chrome'
    }
    
    stages {
        stage('安装依赖') {
            steps {
                sh 'pip install -r requirements.txt'
            }
        }
        
        stage('执行测试') {
            steps {
                sh '''
                    pytest testcases/ \
                        --alluredir=reports/allure-results \
                        --headless \
                        -n auto \
                        --reruns 2 \
                        -v
                '''
            }
            post {
                always {
                    // 无论测试成功还是失败都生成报告
                    allure includeProperties: false,
                           results: [[path: 'reports/allure-results']]
                }
            }
        }
    }
    
    post {
        // 发送邮件通知
        failure {
            emailext(
                subject: "[自动化测试] 构建失败 - ${env.JOB_NAME}",
                body: "详情见:${env.BUILD_URL}",
                to: 'test-team@company.com'
            )
        }
    }
}

七、常见问题排坑指南

问题 原因 解决方案
selenium.common.exceptions.WebDriverException 浏览器驱动版本不匹配 使用 webdriver-manager 自动管理
元素定位超时 页面加载慢/元素未渲染 增加隐式等待时间,或使用显式等待
Chrome自动关闭 Chrome版本更新导致驱动不兼容 pip install --upgrade webdriver-manager
pytest: error: unrecognized arguments allure命令行参数未识别 pip install allure-pytest
执行无头模式找不到元素 无头模式viewport不同 添加 --window-size=1920,1080
并行执行用例互相干扰 测试数据未隔离 每个用例使用独立的测试数据
提示"不是私密连接" 证书问题 添加 --ignore-certificate-errors
Allure报告中文乱码 系统字体问题 allure open 命令在报告目录执行

八、总结

框架能力一览

复制代码
Pytest+Selenium UI自动化框架
├── 用例管理:Pytest框架 + conftest.py 统一配置
├── 页面封装:Page Object模式,定位与操作分离
├── 数据驱动:YAML + @pytest.mark.parametrize
├── 失败处理:自动截图 + 失败重跑
├── 并行执行:pytest-xdist 多进程
├── 报告展示:Allure美观可视化报告
├── CI集成:Jenkins Pipeline一键运行
└── 日志追踪:控制台+文件双输出

下一步可以做什么

  • 增加API测试 :结合 requests + Pytest 做接口自动化
  • 数据库校验:用例中直接查询数据库验证数据正确性
  • 邮件通知:自定义测试结果通知策略
  • 测试平台化:将框架包装成Web平台,非技术人员也能执行

📌 核心代码已开源到本文示例项目。如果你想看更完整的实战项目(包含更多页面对象、复杂场景处理、多环境切换等),欢迎收藏+关注,下一篇我们深入讲讲怎么把这个框架用在实际项目里。
🔥 下一篇预告:《Charles抓包实战-10个测试必会场景》------接口调试、HTTPS抓包、断点修改请求、模拟弱网、Map Local挡板...每个场景一节实操。

相关推荐
C+-C资深大佬12 小时前
GitHub Actions 自动化运维实战:从零到一构建高效 CI/CD 流水线
运维·自动化·github
大海变好AI1 天前
AIGC分层推理落地能否串联多工具完成自动化闭环
人工智能·python·自动化·aigc
工业HMI实战笔记1 天前
【无标题】
大数据·人工智能·ui·自动化·人机交互·交互
曦尧1 天前
Claude Agent Skills:用文件夹封装专业流程,AI 应用从「提示工程」走向「能力工程」
ai·自动化
增量星球1 天前
《持续交付2.0系列六》业务需求协作管理
java·运维·自动化·devops·持续部署·持续集成
工业HMI实战笔记1 天前
【拯救HMI】:边缘计算在工业自动化中的落地:低延迟控制的实现路径
人工智能·自动化·边缘计算
qq_349447951 天前
LLMops实战:大模型微调、部署、评估自动化
运维·自动化
天才测试猿1 天前
Selenium自动化测试网页加载太慢怎么解决?
自动化测试·软件测试·python·selenium·测试工具·职场和发展·测试用例