
简介这是一份基于PythonSelenium的web自动化测试框架设计与实现方向的毕业设计文档适合软件测试初学者、自动化测试工程师以及正在搭建Selenium框架的开发者学习参考。内容从传统手工测试容易产生疲劳和测试盲点的弊端切入系统讲解软件测试理论基础、自动化测试分类、Web应用测试内容与框架需求分析并结合具体实现说明如何利用Selenium完成UI布局、兼容性、稳定性等自动化测试让读者能把握自动化测试生命周期、高质量测试过程建立等关键环节。资源为doc格式压缩包包含1个文件整体大小约1.53MB下载后可直接阅读与打印。目前已有497人学习下载对于测试方向学习者有不错的参考价值。文档内含中英文摘要、目录及完整章节深入阐述自动化测试框架的分类、设计与实现过程还涉及人工智能、机器学习等未来发展方向的探讨既可用于相关论文写作、课程设计参考也可作为实际搭建测试框架时的设计蓝本。1. 基于 PythonSelenium 的 web 自动化测试框架从脚本到能被团队接管的工程不管你是刚接触 Selenium 的测试新人还是已经写了几年“能跑就行”的自动化脚本最后大概率都会撞上同一个问题脚本越写越多但维护它们的时间已经快超过手工回归的时间了。元素定位写得到处都是前端一改版几十个用例集体报错根本不知道是哪一层出了问题也没人敢拍胸脯说这套东西能在他自己的电脑上跑起来。这篇文章要聊的就是怎么把散落的 Selenium 脚本整理成一个有分层、有公共封装、有配置管理、有报告输出的自动化测试框架——也就是标题里那句“设计与实现”真正在讲的事。框架的本质不是引入多少新工具而是定规矩用例该怎么写、页面对象怎么放、浏览器驱动怎么管、失败截图存到哪、报告怎么生成。Python 负责粘合Selenium 负责驱动浏览器两者的分工本身就决定了框架的结构。下面就从最核心的分层讲起再落到能直接抄走的工程搭建和实战用例最后用两个高频痛点——登录态复用和失败重试——收尾。整个过程中参数怎么调、坑在哪都会给到具体做法。2. 设计框架前先理解分层为什么裸写 Selenium 跑不长2.1 脚本堆叠式的自动化为什么活不过三个月直接写 Selenium 脚本跑用例入门门槛很低webdriver.Chrome()、find_element(By.ID, username)、send_keys()几条 API 一拼一个登录用例就通了。但这样的代码一旦超过十个用例问题就开始成片出现。最常见的情况是登录逻辑被复制到了每个用例里一个登录框的定位变化所有用例都要改测试数据写死在脚本里换一套环境就得全文搜索替换浏览器配置、等待策略、失败截图散落在各处根本没有统一的出口。所以一个能被长期维护的 web 自动化测试框架第一原则就是分层——让每一类职责待在一个明确的目录里不让用例层的人去关心浏览器是怎么启动的也不让页面对象层的人去关心报告文件名的格式。Selenium 官方文档一直没有强制给出“你应该这样组织代码”但社区在大量实战后沉淀出了一个公认的最简分层结构。2.2 四层结构用例层、业务层、页面对象层、公共层实际落地时我一般会把框架切成下面四层每一层的职责边界要非常清楚层级目录/模块职责典型内容用例层testcases/业务场景的组装只做“安排”不做执行细节test_login.py, test_order.py业务层business/跨页面的业务流程比如下单要经过搜索页、详情页、购物车订单流程、支付流程页面对象层pages/每一个页面封装成一个类元素定位和数据操作都在类里LoginPage, SearchPage公共层common/与业务无关的通用能力所有层都能调用browser.py, config.py, logger.py, report.py页对象层尤其是框架的命根子。一个页面写一个类类里面只做两件事描述这个页面上有哪些元素、这些元素能做什么操作。比如登录页的LoginPage里面既不应该有“断言登录成功”的逻辑更不应该有生成测试报告的逻辑它的职责是暴露input_username()、input_password()、click_login()这些方法让用例层去组合。前端改版时最坏情况下只需要改对应页对象的内部定位用例层的代码一行都不用动。这样一来框架的每一层都可以独立演化公共层升级了浏览器启动方式页面对象层和用例层完全无感知用例层新增了一条测试场景公共层和页面对象层也完全不用动。这就是分层的价值——把变化隔离在最小范围内。2.3 热加载配置环境切换不需要改代码框架的配置管理一定要做的第一件事就是把环境相关的信息从代码里剥离出来。URL、账号、超时时间、浏览器类型、是否无头模式这些都是环境配置不是业务代码。常见的做法是在工程根目录放一个 YAML 或者 JSON 配置文件公共层写一个ConfigManager来读取。我一般会推荐 YAML因为它的注释能力比 JSON 好字典嵌套的写法也更直观。配置文件大约长这样# config/config.yaml env: test base_url: https://demo.example.com browser: type: chrome # chrome / firefox / edge headless: false # 服务器上跑建议 true implicit_wait: 10 # 隐式等待秒数 page_load_timeout: 30 user: username: tester01 password: Pssw0rd report: path: reports/ title: Web Auto Test Report公共层里的配置读取模块核心逻辑就是把这个 YAML 解析成 Python 字典然后供全工程调用import yaml from pathlib import Path class ConfigManager: 全局配置管理启动时读取一次 YAML后续通过实例属性访问 def __init__(self, config_path: str config/config.yaml): self._config self._load(config_path) def _load(self, config_path: str) - dict: path Path(config_path) if not path.exists(): raise FileNotFoundError(f配置文件不存在: {path.resolve()}) with open(path, encodingutf-8) as f: return yaml.safe_load(f) def get(self, key: str, defaultNone): # 支持点号路径例如 browser.headless node self._config for part in key.split(.): if not isinstance(node, dict) or part not in node: return default node node[part] return node config ConfigManager()这里的要点是get()方法支持了browser.headless这种点号路径访问省去了在业务代码里写多层的[browser][headless]。这个设计对嵌套深的配置结构非常关键——你的配置项一旦多起来每次取值都手写路径不仅啰嗦而且容易在None上报错。配置层还有一种更进阶的用法用环境变量覆盖 YAML 里的值。比如在 Jenkins 上跑测试时测试环境地址肯定和本机不一样如果在 YAML 里写死就得在 CI 机器上再维护一份配置文件。常见做法是在ConfigManager的get()方法里增加一个逻辑如果存在同名环境变量就用环境变量的值覆盖 YAML 里的值这能让框架适配不同执行环境。3. 搭建工程骨架浏览器驱动、目录结构和第一个能跑的用例3.1 环境安装webdriver-manager 是最省心的选择很多初学者在搭建 Selenium 环境时卡在浏览器驱动的下载上。Chrome 升级后chromedriver 版本不匹配脚本直接报SessionNotCreatedException。你的本机还能手动下载到了 CI 服务器上就麻烦了。2024 年以后的 Selenium 版本实际上官方已经推荐用webdriver-manager这个库来处理驱动版本匹配的问题它在 Selenium Manager 功能正式集成之前一直是社区事实标准。安装依赖很简单建议使用虚拟环境隔离python -m venv .venv source .venv/bin/activate pip install selenium webdriver-manager pytest pytest-ordering安装过程如果网速不行可以换用国内镜像源。装完后最简单的浏览器启动代码是这样的# common/browser.py from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager def create_driver(headless: bool False): 创建 Chrome 浏览器实例自动匹配本机 Chrome 版本对应的驱动 service Service(ChromeDriverManager().install()) options webdriver.ChromeOptions() if headless: options.add_argument(--headlessnew) options.add_argument(--window-size1920,1080) options.add_argument(--disable-gpu) options.add_argument(--no-sandbox) return webdriver.Chrome(serviceservice, optionsoptions)这里的ChromeDriverManager().install()会自动检测你本机安装的 Chrome 版本号下载匹配的 chromedriver 并缓存在用户目录。如果本机还没装 Chrome它会直接报错提示——这个依赖关系要清楚webdriver-manager 管的是驱动不管浏览器本体。生产环境里 CI 机器建议预装固定版本的 Chrome并且锁定service Service(ChromeDriverManager(driver_version114.0.5735.90).install())不要每次构建都去检查最新版否则版本漂移会让你某天早上突然收到一批失败用例。3.2 目录结构一张图看懂整个工程工程搭出来后的目录结构如下每个目录的职责要跟团队说清楚不然很快就有人乱放了web_auto_framework/ ├── config/ │ └── config.yaml # 环境配置、浏览器参数、账号信息 ├── common/ │ ├── __init__.py │ ├── browser.py # 浏览器驱动创建与销毁 │ ├── config.py # 配置读取 │ ├── logger.py # 日志封装 │ └── report.py # HTML 报告生成 ├── pages/ │ ├── __init__.py │ ├── base_page.py # 页面对象基类封装显式等待 │ ├── login_page.py │ └── home_page.py ├── testcases/ │ ├── __init__.py │ ├── conftest.py # pytest fixtures │ └── test_login.py ├── reports/ └── requirements.txtbase_page.py 是整个页面对象层的地基。Selenium 原生的find_element不带等待页面加载慢一点就直接NoSuchElementException。所以我在基类里统一封装了显式等待的逻辑这样所有页面对象都共用一套等待策略等待超时时报错信息也统一格式。# pages/base_page.py 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, timeout: int 10): self.driver driver self.timeout timeout def find_element(self, locator): 显式等待元素出现后返回元素对象超时抛出带定位信息的异常 element WebDriverWait(self.driver, self.timeout).until( EC.presence_of_element_located(locator) ) return element def click(self, locator): 等待元素可点击后点击 element WebDriverWait(self.driver, self.timeout).until( EC.element_to_be_clickable(locator) ) element.click() def input_text(self, locator, text: str): 清空输入框并输入文字 element self.find_element(locator) element.clear() element.send_keys(text)这里有个细节值得单独说find_element里的locator是一个元组比如(By.ID, username)。把这个元组作为参数在各层之间传递是 Selenium 比较规范的做法——不要用“字符串拼接表达式”的方式去构造定位器那会让代码完全不可维护。element_to_be_clickable比presence_of_element_located更严格它不但要求元素在 DOM 里还要求可见并且可用所以用在 click 操作上更合适。input_text里先 clear 再 send_keys 是必要的不要假设输入框初始是空的。3.3 启动浏览器只能用一次session 级 fixture 的关键作用pytest 的 fixture 机制是连接测试代码和框架的桥梁。我之前看到很多人把浏览器启动写在setup_method里每个用例都是全新的浏览器一个用例场景如果有 5 步操作每步都重新开一个浏览器光启动时间的开销就能让一套用例的耗时长十几倍。正确的做法是把 driver 定义成 session 级别的 fixture整个测试周期内只有一个浏览器实例所有用例共享用例之间通过操作步骤去跳转页面。下面是一个能直接抄进 conftest.py 的版本# testcases/conftest.py import pytest from common.browser import create_driver from common.config import config pytest.fixture(scopesession) def driver(): 整个测试会话共用一个浏览器实例结束后自动关闭 _driver create_driver(headlessconfig.get(browser.headless, False)) _driver.maximize_window() yield _driver _driver.quit() pytest.fixture(scopesession) def base_url(): 读取测试环境根地址 return config.get(base_url) pytest.fixture(scopesession) def login_info(): 读取测试账号信息 return { username: config.get(user.username), password: config.get(user.password), }看到这里你可能会有疑问session 级共享一个浏览器那多个用例的执行顺序不是互相影响吗比如登录用例跑完了会话还在下一个用例是否不需要再登录了这个问题的答案需要想清楚如果下一个用例恰好是一个需要登录态的操作那它应该依赖“前置条件已满足”。所以用例层要谨慎使用 session 共享的浏览器通常在同一个模块下的用例按顺序执行问题不大但跨模块的场景最好让每个用例的关键前置步骤保持完整。conftest.py里还有一个高频用法是失败截图。用例失败时自动截图、自动附加到日志这在排查问题的时候帮助特别大。这个能力可以通过 pytest 的pytest_runtest_makereport钩子来实现等框架能跑通之后随时可以加上。3.4 第一个用例跑通不要急着写业务先验证框架框架搭好的第一个验证用例不要直接写登录、写下单先写一个人人都会的操作打开百度首页断言标题。这个用例的意义不是测百度而是验证浏览器驱动、配置读取、webdriver-manager 三者连通没问题所有环境变量在目标机器上都是通的。# testcases/test_smoke.py import pytest def test_open_baidu(driver, base_url): driver.get(https://www.baidu.com) assert 百度 in driver.title print(f当前页面标题: {driver.title})在这个用例里driver直接来自 conftest.py 的 fixturebase_url参数没用到但传进来了方便后续扩展。如果这个用例在你的机器上 30 秒内跑通说明框架最核心的部分已经成功。如果create_driver卡住或者报错优先检查 Chrome 版本和 chromedriver 的匹配问题——用chrome://version/查看版本号再用 webdriver-manager 手动触发一次下载确认驱动版本没选错。这一步排查完剩下的工作就是往框架里填页面对象和用例。4. 实战用页面对象模型写登录用例并从零搭一个测试页面4.1 没有测试环境怎么办用 Mock 服务解决依赖问题实际负责过自动化项目的人肯定有这种体验被测系统的测试环境不稳定今天能打开明天 502更多时候是根本不知道环境地址在哪。网上很多教程用 GitHub 上的开源项目做演示那你就要考虑网络和教育网的兼容性很多网络环境下那些网站根本打不开。用 Flask 自己搭一个最小可用的测试页面是一个稳妥的选择。它能让你完全掌控页面元素而且不管在什么网络环境下都能跑通。不要觉得 mock 出来的页面不真实实际上 mock 页面在接口联调和自动化预演阶段的价值很大而且你用 mock 搭出来的登录页结构和真实系统登录页几乎是一样的。以下是一个供 Selenium 操作的最小登录页面服务端代码。保存为mock_app.py# mock_app.py from flask import Flask, request, redirect, session, render_template_string app Flask(__name__) app.secret_key test-secret # 页面模板一个最简登录表单 HTML !DOCTYPE html html headtitleMock Login/title/head body form methodpost action/login input typetext idusername nameusername placeholder用户名 / input typepassword idpassword namepassword placeholder密码 / button typesubmit idlogin-btn登录/button /form div idmsg styledisplay:none/div /body /html app.route(/, methods[GET]) def index(): if session.get(logged_in): return 已登录 return render_template_string(HTML) app.route(/login, methods[POST]) def login(): username request.form.get(username) password request.form.get(password) if username admin and password admin123: session[logged_in] True return redirect(/) return 用户名或密码错误, 401 if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)启动 mock 服务python mock_app.py它就在本机 5000 端口提供登录页面。这个 mock 服务要包含两个路径根路径返回登录表单/login路径负责校验账号。为了更贴近真实系统username和password两个字段名要和常见系统保持一致。4.2 LoginPage 与 HomePage页面对象层怎么落地有了 mock 服务就可以写真正的页面对象了。登录页面LoginPage继承自BasePage对外暴露的是语义化操作内部藏着具体的定位器。业务代码只关心输入账号、输入密码、点登录不关心这些元素是什么 id 什么 name这是页面对象模型的核心价值。# pages/login_page.py from selenium.webdriver.common.by import By from pages.base_page import BasePage class LoginPage(BasePage): 登录页面对象封装登录相关的所有操作 # 定位器集中在类属性改版时只改这里 USERNAME_INPUT (By.ID, username) PASSWORD_INPUT (By.ID, password) LOGIN_BUTTON (By.ID, login-btn) def input_username(self, username: str): self.input_text(self.USERNAME_INPUT, username) def input_password(self, password: str): self.input_text(self.PASSWORD_INPUT, password) def click_login(self): self.click(self.LOGIN_BUTTON) def login(self, username: str, password: str): 组合操作一步完成登录流程 self.input_username(username) self.input_password(password) self.click_login()定位器集中放在类属性顶部是为了让页面元素变更的影响面最小化。在实际项目中建议定位器不要散在方法里而是统一集中在页面类前面这样看到页面类就能知道这个页面有哪些关键元素。login()方法把三步操作组合在一起用例层可以直接调用不需要每个用例都重复写三次调用的代码。组合操作在框架里叫业务步骤可以根据场景自由组合。4.3 在 pytest 里写登录用例并使用参数化把异常场景覆盖上页面对象写好后登录用例就变成简单的组装工作。pytest 的参数化能力在这里很实用一批账号密码组合可以生成多个用例不必为每个数据单独写一条代码。# testcases/test_login.py import pytest from pages.login_page import LoginPage class TestLogin: 登录场景测试用例 pytest.mark.parametrize(username,password,expect, [ (admin, admin123, 已登录), (admin, wrong, 用户名或密码错误), (, , 用户名或密码错误), ]) def test_login_scenarios(self, driver, base_url, username, password, expect): driver.get(base_url) login_page LoginPage(driver) if username admin and password admin123: login_page.login(username, password) assert expect in driver.page_source else: login_page.login(username, password) assert expect in driver.page_source用例层干的事情非常纯粹把页面对象的方法组合成一条业务场景然后用断言验证结果。参数化用的三组数据分别覆盖了正常登录、密码错误、空数据三个场景。注意这里expect的断言用的都是driver.page_source里的文本虽然不够优雅但对于一个 mock 页面来说够用了真实项目中建议在页面上加一个专门的元素比如登录成功后的用户名来断言定位到具体元素再断言它的文本。不要对driver.page_source做空泛的断言那样可能因为页面上静态资源加载不全导致误判。参数化之所以把“输入错误密码”和“输入空密码”放在一起是因为它们的预期都是一个“401 页面”。但值得注意的是示例代码里login()方法没有处理“登录失败时页面是否停留在登录页”这个断言。更细化的用例可以这样写登录失败后仍然能找到USERNAME_INPUT元素说明页面没有跳转并且页面上出现了错误提示文本。这样的断言逻辑更接近真实业务场景。5. 运行、报告与持续集成的两种做法Hook 钩子和日志追踪5.1 失败自动截图方案用户最关心的往往是“用例挂了能不能让我一眼看到现场”。添加失败截图是一个能直接提升分析效率的功能。pytest 有pytest_runtest_makereport钩子它会在每个测试方法执行完成后被调用如果结果里failed是True就调用 driver 的get_screenshot_as_file方法。截图保存的路径要按日期和用例名组织比如reports/screenshots/20240912_test_login_失败原因.png。把截图路径放进日志同时给 HTML 报告设置附件就能在统一入口看到现场。# testcases/conftest.py pytest.hookimpl(hookwrapperTrue) def pytest_runtest_makereport(item, call): outcome yield report outcome.get_result() if report.when call and report.failed: driver item.funcargs.get(driver) if driver is not None: screenshot_dir Path(reports/screenshots) screenshot_dir.mkdir(parentsTrue, exist_okTrue) screenshot_path screenshot_dir / f{item.name}_{int(time.time())}.png driver.save_screenshot(str(screenshot_path)) report.screenshot str(screenshot_path)report.when的判断非常重要pytest 一个用例会触发 setup、call、teardown 三个阶段只需要在call阶段也就是用例体本身执行完毕判断失败。如果你不加这个判断setup 出错时也可能尝试截图而此时 driver 可能根本没有创建成功。item.funcargs.get(driver)这个取法可以拿到当前用例 fixture 的实参比从item内部去翻 fixture 要直观得多。adding screenshot to report 的方式配合 pytest-html 插件的--self-contained-html参数能把截图嵌入 HTML 文件里别人打开一个报告文件就能看到失败现场。5.2 HTML 报告不是随便生成的Selenium 自动化最不体面的结果是用例跑完了给别人看一个控制台输出。pytest-html 是目前最普及的报告方案。用法非常直接运行命令加上参数即可pytest testcases -v --htmlreports/report.html --self-contained-html--self-contained-html这个参数很有必要它会把 CSS、JS 和截图都嵌入到单文件 HTML 里方便直接分发和邮件附件。但要注意pytest-html 的报告标题默认是你的测试目录名想要自定义标题要加一行插件配置。更稳妥的做法是在pytest.ini里一次性把常用参数固定下来# pytest.ini [pytest] addopts -v --tbshort --htmlreports/report.html --self-contained-html testpaths testcases--tbshort控制了失败堆栈的详细程度。默认的 long 模式会打印过多无关代码特别是在 selenium 这种库的内部调用上非常冗长short 模式只保留最关键的链条排查定位失败问题足够用了。testpaths限定了 pytest 只去testcases/目录下收集用例避免它去扫描pages/目录里那些以 test 开头的文件而产生误收集。5.3 本地跑和 CI 跑要区分开来的两个细节本地跑测试和 CI 的 Jenkins / GitLab CI 里跑测试最大的差异在两点。第一是浏览器需要无头模式在服务器上没有显示器如果不开启 headless 浏览器会直接启动失败。第二是驱动下载的稳定性webdriver-manager 在 CI 里可能因为网络受限下载不了驱动这时要在 CI 机器的镜像里预先下载好驱动并配置PATH环境变量。如果团队用的是 Docker 跑测试常见做法是直接使用现成的 Selenium 官方镜像并把驱动问题抛在镜像外解决docker run -d --name chrome -p 4444:4444 -p 7900:7900 selenium/standalone-chrome然后用 Selenium 的 Remote WebDriver 方式连接这个固定地址。这样本地代码不变只把create_driver里的webdriver.Chrome换成webdriver.Remote构造参数里加一个command_executor指向http://localhost:4444/wd/hub。这种做法的额外收益是浏览器版本被固定在镜像里不受本机 Chrome 自动升级影响测试结果的可复现性大幅提升。但要注意镜像的拉取需要内网代理或镜像加速器这一点是工程化落地时要提前评估的。5.4 从运行日志里定位问题最后再补一个日常排错的技巧。在框架里封装好日志模块之后每次执行都要带着日志跑。pytest 运行后日志输出到哪里这是一个高频问题。建议在logger.py里同时往控制台和文件输出日志文件按logs/20240912.log这样的格式每天一个# common/logger.py import logging from datetime import datetime def setup_logger(name: str auto) - logging.Logger: logger logging.getLogger(name) logger.setLevel(logging.INFO) fmt logging.Formatter(%(asctime)s - %(levelname)s - %(message)s) timestamp datetime.now().strftime(%Y%m%d) fh logging.FileHandler(flogs/{timestamp}.log, encodingutf-8) fh.setFormatter(fmt) ch logging.StreamHandler() ch.setFormatter(fmt) logger.addHandler(fh) logger.addHandler(ch) return loggerlogger.py要注意.addHandler()不能重复调用否则日志会打印两遍。如果你观察到一个用例的日志出现了两次大概率是 logger 实例被多次创建解决方案是在模块底部加这句logger setup_logger()flie 和 console 双输出的价值在于控制台实时看执行过程文件留底。一旦测试结束直接打开当天 log 文件搜索ERROR或者FAILED就能快速定位到失败用例。配合失败截图功能基本不需要再手动打开浏览器去复现问题了。6. 框架进阶的两个运用复用登录态与失败重试机制跑通了基础用例之后框架真正面对复杂业务时还有两个绕不开的关键问题登录态的最高效利用以及用例稳定性不足时的自动重试。6.1 复用登录态让所有用例不再重复登录大多数测试场景有一个共同的痛点登录是最大的时间开销之一。一百个用例都从登录开始跑光登录这一步就要占掉整个套件执行时间的三分之一。复用登录态并不是偷懒而是合理利用浏览器的 profile 机制。Selenium 在启动 Chrome 时可以指定一个 user-data-dir浏览器会把 Cookie、LocalStorage、Session 都写到这个目录。第一次执行登录用例后这个目录里已经存在有效会话后续测试启动 Chrome 时直接加载该目录浏览器就能直接处于登录状态跳过登录步骤。以下是改造方案from selenium import webdriver def create_driver_with_profile(profile_dir: str /tmp/chrome_profile, headless: bool False): 复用指定的 Chrome 用户数据目录保留登录态 options webdriver.ChromeOptions() options.add_argument(f--user-data-dir{profile_dir}) if headless: options.add_argument(--headlessnew) options.add_argument(--no-sandbox) return webdriver.Chrome(optionsoptions)用法上要注意指定 user-data-dir 之后不能再同时用默认的 profile。第一次执行时先手动访问被测系统并登录一次然后把该目录保留下来供后续运行挂载。但这种方法有一个非常容易出现的问题--user-data-dir指定的路径如果被上一次残留的非正常关闭的浏览器进程锁定Chrome 会启动失败遇到这种情况排查时先杀掉残留的 chromedriver 和 chrome 进程删除该目录后重来。另外要特别注意安全边界登录态属于敏感数据不要把 profile 目录提交到 Git 仓库建议在.gitignore里显式忽略/tmp/chrome_profile或者类似目录。持续集成服务器上每个构建都用一个全新目录不要在 CI 上复用 profile否则很容易出现会话过期但用例仍然当作通过的情况。6.2 失败重试机制测试框架为稳定性兜底自动化用例最讨厌的误报之一是页面上某个接口超时实际功能没变化但用例失败了。没有重试机制的时候只能人工确认后手动重跑有重试机制就能把这类波动自动消化。pytest-rerunfailures 插件是比较成熟的方案用法极其简单pip install pytest-rerunfailures pytest testcases -v --reruns 2 --reruns-delay 3参数含义很直白--reruns 2代表用例失败后最多重跑 2 次--reruns-delay 3代表每次重跑间隔 3 秒。间隔不要太短否则网络抖动还没恢复就重试等于没重试。但注意重试机制只适用于偶发性的环境问题如果断言本身写错了、定位器写错了重试一万次也是失败。所以要把重试次数控制在 2-3 次之内并且重试后的每次失败都要保留完整的日志和截图方便确认到底是“环境原因自动恢复了”还是“功能真的坏了”。结合这两点技巧框架最终就形成了一个闭环复用登录态提高速度重试机制提升稳定性报告与日志保证失败现场的可追溯性。真正验证框架好不好用的方式不是功能写得多炫而是把执行时间拉长跑三天看看每天的 Green Rate 和误报率。能把这两个指标稳定下来这套基于 PythonSelenium 的框架才算是真正交付到了团队手里。本文还有配套的精品资源点击获取