Selenium自动化测试框架从零搭建:Python+pytest+PO模式实战

发布时间:2026/9/9 14:34:31
Selenium自动化测试框架从零搭建:Python+pytest+PO模式实战 做Web自动化测试的谁没在Selenium上栽过几个跟头最简单的脚本能跑通一到了实际项目里脚本堆成山、用例跑两天就开始红定位器改一处崩一片执行到一半浏览器就无响应。老实说Selenium本身不难难的从来都是“框架”这两个字——怎么把代码组织得能维护、能复用、能稳定跑才是真正的分水岭。这篇文章我就从零开始分享一套可以快速落地的Selenium自动化测试框架搭建方案。整套框架基于Python Selenium pytest Page Object模式会覆盖目录结构设计、浏览器驱动版本判断、BasePage封装、页面对象写法、用例组织、报告输出以及我这些年踩过的坑和排查思路。适合刚接触Web自动化、想从“脚本式”写法升级到“框架式”写法的测试工程师也适合需要快速交付一套可用框架的团队参考。1. 框架整体设计与思路拆解1.1 为什么选Selenium pytest这套组合你可能会问市面上有Cypress、Playwright这些新工具为什么还要用Selenium我的观点是Selenium依然有不可替代的位置。它支持的语言最多、浏览器覆盖面最全、生态历史最久尤其是老项目的回归测试、跨浏览器兼容性验证新工具不一定接得住。Selenium WebDriver是W3C标准实现意味着你有任何问题几乎都能在社区里找到答案。那为什么搭配pytest而不是unittestpytest的fixture机制实在太适合做自动化测试框架的底座了。一个conftest.py就能搞定浏览器启动、退出、失败截图、日志收集用例里只需要专注业务操作。再加上pytest天然支持参数化、插件生态丰富allure报告一行命令就能接上。这些能力如果用unittest去做会多写很多胶水代码维护成本也更高。Page Object模式PO模式是这套框架的骨架。核心思想很简单把页面中的元素定位和操作逻辑封装成一个个页面类测试用例只调这些类的业务方法不直接接触driver.find_element这类底层操作。以前我见过不少团队脚本里到处是driver.find_element(By.ID, xxx).click()一旦产品改了按钮ID几十个用例跟着改那就是灾难。PO模式把改动的半径缩小到一个页面类里这才是能长期维护的关键。1.2 目录结构设计每层各司其职框架要做到清晰目录结构要在一开始就定好不然越写越乱。我常用的结构是下面这样project/ ├── config/ │ ├── __init__.py │ └── settings.py # 全局配置环境地址、超时时间、浏览器类型 ├── pages/ │ ├── __init__.py │ ├── base_page.py # BasePage基类封装通用操作 │ ├── login_page.py # 登录页对象 │ └── cart_page.py # 购物车页对象 ├── testcases/ │ ├── __init__.py │ ├── conftest.py # fixture驱动管理、失败截图 │ └── test_cart.py # 购物车相关用例 ├── data/ │ └── test_data.py # 测试数据统一管理 ├── utils/ │ ├── __init__.py │ └── log_util.py # 日志封装 └── reports/ # 测试报告输出目录这个设计有两个好处。第一你拿到任何新项目往目录里填东西就行不用临时想文件放哪第二人和人之间协作时代码位置是约定的不用互相问。pages目录放页面对象testcases只放测试用例config管所有可变配置utils放公共工具类层次非常清楚。关于分层我自己有个体会目录不是越多越好。见过有人搭了十来个包结果一个页面类里才十几个方法空架子一大堆反而是负担。框架的粒度够用就行等真的出现复用需求或者改造需求时再加层不要过早设计。2. 环境搭建与浏览器驱动先把地基打稳2.1 Python虚拟环境与依赖安装我强烈建议新项目一定用虚拟环境不要图省事直接装在全局环境。原因很简单不同项目的依赖版本可能冲突今天这个项目要Selenium 4.15明天那个项目还在用3.141全局环境会让你疯掉。Python 3.8以上版本都自带venv操作也不复杂。# 创建并激活虚拟环境 python -m venv .venv source .venv/bin/activate # Windows下用 .venv\Scripts\activate # 安装核心依赖 pip install selenium pytest pytest-html allure-pytest这里有一个原则指定版本号安装。直接用pip install selenium装的是最新版但你的代码如果写过一段时间升级可能带来行为变化。我更推荐在项目里用requirements.txt把版本锁定selenium4.15.0 pytest7.4.3 pytest-html4.1.0 allure-pytest2.13.2锁版本不是保守是稳定。自动化测试框架最大的敌人就是不确定依赖版本变来变去出了问题都说不清是代码问题还是环境问题。2.2 浏览器驱动版本怎么判断应该下载哪个这个问题每天都在各种技术群里出现。驱动下载最核心的逻辑是驱动的主版本号必须和浏览器的主版本号一致。比如浏览器是Chrome 118驱动就要下载118.x.x浏览器是Chrome 119就下载119.x.x。不用完全一模一样次版本号有差异通常也能跑但主版本号差一位基本就不行。判断浏览器版本的方法打开Chrome点右上角三个点 - 帮助 - 关于Google Chrome弹出的页面里会显示版本号比如118.0.5993.88前面这个118就是主版本号。然后去Chrome for Testing官网找对应版本的ChromeDriver。Windows用户下载chromedriver_win32.zipmacOS用户看是Intel芯片还是Apple Silicon分别选mac-x64或mac-arm64。Linux服务器选linux64。下载后的驱动放哪里两种常见方式。第一把驱动所在目录添加到系统PATH环境变量Selenium会自动找到第二直接把chromedriver可执行文件放进Python解释器同目录或项目根目录。我个人更推荐第一种因为多种浏览器驱动都能统一管理。另外有个小技巧放驱动时注意可执行权限Linux和macOS下经常忘了chmod x chromedriver结果一直报权限错误。顺便说一句Selenium 4.6版本以后引入了Selenium Manager如果你没手动下载驱动它会尝试自动管理驱动。能省不少事但它需要能访问外网如果公司网络受限可能就拉不下来。所以最稳妥的做法还是手动确定版本并放置驱动这样可控性最强。2.3 Selenium 4对比旧版的改进点既然从零搭框架直接用Selenium 4没必要回头看老版本。Selenium 4最大的变化是WebDriver协议演进到W3C标准化同时新增了相对定位器Relative Locator和更好的窗口标签页管理。另外以前通过driver.find_element_by_id这类方法在4.x里全被移除了统一用driver.find_element(By.ID, ...)。有一点要适应4.x对等待逻辑更严格元素定位建议配合WebDriverWait使用。你如果从老教程拷贝代码可能看到大量find_element_by_xpath的写法那些代码在Selenium 4里直接报错。这也是做技术选型时要把控好的一个风险点新项目一定要基于新API写别走回头路。3. 核心代码实现从BasePage到用例跑通3.1 BasePage封装把重复劳动收敛起来BasePage是框架里复用率最高的类。它不应该包含任何具体页面的业务逻辑只封装所有页面通用的操作。比如查找元素、点击、输入、获取文本、等待元素出现、截图。我把这个类控制得尽可能薄每个方法只做一件事这样每个页面类继承它的时候语义非常清楚。# pages/base_page.py import logging from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException logger logging.getLogger(__name__) class BasePage: def __init__(self, driver, timeout10): self.driver driver self.wait WebDriverWait(driver, timeout) def find_element(self, locator): 查找单个元素自动等待元素可被找到 return self.wait.until(EC.presence_of_element_located(locator)) def find_clickable_element(self, locator): 查找元素并确保可点击 return self.wait.until(EC.element_to_be_clickable(locator)) def click(self, locator): 点击元素 element self.find_clickable_element(locator) element.click() logger.info(f点击元素: {locator}) def input_text(self, locator, text): 输入文本先清空再输入 element self.find_element(locator) element.clear() element.send_keys(text) logger.info(f向 {locator} 输入文本: {text}) def get_text(self, locator): 获取元素文本 element self.find_element(locator) return element.text def wait_for_visible(self, locator): 等待元素可见 return self.wait.until(EC.visibility_of_element_located(locator)) def take_screenshot(self, filename): 失败时截图保存 self.driver.save_screenshot(filename) logger.info(f截图已保存: {filename})这里有几个关键细节值得展开。第一find_element用的是presence_of_element_located它只关心元素在不在DOM里不关心可不可见、可不可点击。而click方法里的element_to_be_clickable会等到元素可见且可用。为什么要区分因为页面上有些元素虽然渲染出来了但还处于禁用状态直接click会报ElementClickInterceptedException。用不同的等待条件去匹配不同操作场景才能真正减少偶发失败。第二input_text里先clear()再send_keys()这个顺序不要反。很多测试脚本失败在输入这一步是因为输入框里默认有值直接send_keys会把内容追加在后面。先清空再输入逻辑才可靠。第三所有元素定位都用locator元组来传也就是(By.XPATH, //input[idusername])这种形式而不是每次直接传字符串给find_element。这样页面类里可以像常量一样定义元素组件变更时只改一处。3.2 页面对象实战以购物车页面为例有了BasePage具体页面类就很好写了。我用购物车页面来举例它是电商项目里很典型的场景涉及元素多、状态多、校验多非常有代表性。一个普通的购物车页面至少包含商品列表、数量增减按钮、删除按钮、勾选商品、价格合计、去结算按钮这些元素。# pages/cart_page.py from selenium.webdriver.common.by import By from pages.base_page import BasePage class CartPage(BasePage): # 元素定位 cart_item_list (By.CLASS_NAME, cart-item) item_checkbox (By.CLASS_NAME, item-checkbox) item_price (By.CLASS_NAME, item-price) item_quantity_input (By.CLASS_NAME, item-quantity) increase_btn (By.CLASS_NAME, increase-btn) decrease_btn (By.CLASS_NAME, decrease-btn) delete_btn (By.CLASS_NAME, delete-btn) total_price_label (By.CLASS_NAME, total-price) checkout_btn (By.CLASS_NAME, checkout-btn) def get_cart_item_count(self): 获取购物车商品种类数 items self.driver.find_elements(*self.cart_item_list) return len(items) def get_total_price(self): 获取页面展示的总价 return self.get_text(self.total_price_label) def increase_quantity(self): 点击数量增加按钮 self.click(self.increase_btn) def decrease_quantity(self): 点击数量减少按钮 self.click(self.decrease_btn) def delete_first_item(self): 删除第一个商品 self.click(self.delete_btn) def select_first_item(self): 勾选第一个商品 self.click(self.item_checkbox) def goto_checkout(self): 去结算 self.click(self.checkout_btn)你在实际项目里元素定位的id或class不会这么整齐但这些方法的结构是可以直接复用的。页面类只负责操作不做断言。为什么断言也不放页面类因为断言属于用例层的行为放在页面类里会降低可读性用例到底验证了什么得看用例代码才清楚。页面类和方法命名尽量贴近业务语义比如increase_quantity、goto_checkout而不是click_button_1、click_button_2这样测试用例读起来就像在描述业务步骤。3.3 conftest.py与pytest fixture浏览器生命周期管理conftest.py是pytest的核心机制。我们在里面定义一个driver的fixture负责启动浏览器、提供驱动实例、用例结束清理同时处理失败截图。为什么用fixture而不是在每个用例里写setUp/tearDown因为fixture可以天然处理依赖注入用例函数的参数名写driverpytest自动把fixture的返回值传进来代码非常简洁。# testcases/conftest.py import os import pytest from selenium import webdriver from selenium.webdriver.chrome.options import Options from datetime import datetime pytest.fixture(scopefunction) def driver(): 启动Chrome浏览器返回driver实例用例结束后关闭 options Options() options.add_argument(--start-maximized) # 无头模式跑CI时打开 # options.add_argument(--headlessnew) # 避免了一些因为窗口大小导致元素不可点击的问题 driver webdriver.Chrome(optionsoptions) driver.implicitly_wait(3) yield driver # 用例失败时截图 if hasattr(driver, failed) and driver.failed: report_dir reports/screenshots os.makedirs(report_dir, exist_okTrue) now datetime.now().strftime(%Y%m%d_%H%M%S) driver.save_screenshot(f{report_dir}/failure_{now}.png) driver.quit()scopefunction表示每个测试用例都重新启动一个浏览器。好处是用例之间完全隔离互不影响代价是速度慢一些。如果你想节省时间有些人会用scopemodule或class多个用例共用一个浏览器。我的建议是刚搭框架时老老实实用function级别先把稳定做出来再考虑优化。浏览器复用的坑真的很多比如上一个用例的登录状态、弹窗、页面残留都会造成下一个用例定位失败排查起来极其头疼。关于隐式等待和显式等待要特别说清楚。implicitly_wait(3)是先给一个全局兜底意思是查找元素时最多等3秒。BasePage里的WebDriverWait是显式等待按具体条件等最长时间10秒。两种等待不是替代关系而是协作关系。显式等待优先隐式等于是最后一道保障。但要注意Selenium官方文档不建议把隐式等待和显式等待混用因为偶尔会让等待时间叠加。我实际用下来把隐式等待设短一点比如3秒作为兜底不会出大问题。如果你完全依赖显式等待也可以把隐式等待设为0逻辑更纯粹。为什么很多用例偶尔失败一次重跑就好了大多数情况都是等待不够精确导致的。比如点击一个按钮后页面上弹出了新元素代码马上去找下一个元素结果元素还没渲染完成。这时候隐式等待能兜住一部分但有些条件是隐式等待覆盖不了的比如元素存在但不可见、元素被遮挡、属性变化等等。框架里就应该全面转向显式等待每个关键操作都等到对应条件满足。3.4 用例编写pytest风格与数据驱动用例层要写得像业务流程描述。先看一个完整例子# testcases/test_cart.py import pytest from pages.cart_page import CartPage from pages.login_page import LoginPage pytest.mark.cart def test_add_item_to_cart(driver): 新增商品到购物车后商品数量正确增加 login_page LoginPage(driver) login_page.login(test_user, test_pass) cart_page CartPage(driver) initial_count cart_page.get_cart_item_count() cart_page.increase_quantity() new_count cart_page.get_cart_item_count() assert new_count initial_count 1, f商品数量未增加初始{initial_count}新增后{new_count} pytest.mark.cart def test_total_price_display(driver): 购物车总价应为商品单价乘以数量 login_page LoginPage(driver) login_page.login(test_user, test_pass) cart_page CartPage(driver) cart_page.select_first_item() total cart_page.get_total_price() assert total.endswith(元), 总价未正确展示货币单位这里有几个要点。第一用例的命名直接描述业务意图比如test_add_item_to_cart断言失败时从报告里一眼能看出是哪个功能出了问题。不要用test_01、test_02这种编号后期维护根本不知道哪个用例对应什么场景。第二数据驱动用pytest的pytest.mark.parametrize来实现。如果登录模块要验证多种异常输入写法大概是pytest.mark.parametrize(username,password,expected_msg, [ (, 123456, 用户名不能为空), (test_user, , 密码不能为空), (test_user, wrong, 用户名或密码错误), ]) def test_login_invalid(driver, username, password, expected_msg): login_page LoginPage(driver) login_page.login(username, password) assert login_page.get_error_message() expected_msg为什么这么做因为测试数据变了用例代码不用变。产品新增一种校验规则你只需要往参数列表里加一行数据配合allure报告就能看到大量可读的用例记录。这套模式是pytest框架里最值钱的能力之一。3.5 执行测试与生成报告执行用例很简单在项目根目录运行# 执行所有用例生成html报告 pytest testcases/ -s -v --htmlreports/report.html --self-contained-html # 指定标签执行 pytest testcases/ -m cart --htmlreports/report_cart.html --self-contained-html # 配合allure生成更美观的报告 pytest testcases/ --alluredirreports/allure-results allure generate reports/allure-results -o reports/allure-report --clean--self-contained-html这个参数很多人不知道。如果不加生成的HTML报告依赖一堆css和js文件你发给别人或迁移到别的机器样式就丢了。加上它所有资源打进一个HTML文件里分享和归档都非常方便。allure报告是我比较推荐的它能把每个用例的步骤、参数、截图都展示得很清晰。为了配合allure我在用例里会加allure.step装饰器把关键操作变成报告里的一个个步骤排查失败时就像看操作录影一样。import allure allure.step(登录系统) def login(self, username, password): ...4. 常见问题与排查技巧实录4.1 元素定位失败的五个深层原因元素定位失败是Selenium自动化里出现频率最高的问题。这么多年下来我总结出几个高频原因。第一个是动态ID或class。很多前端框架生成的ID每次刷新都不一样比如iditem-123456。你如果直接写死ID下次跑就找不到了。这种场景优先用相对定位找稳定祖先节点再往下找。或者用CSS选择器的部分匹配比如[id^item-]。第二个是元素在iframe里。WebDriver默认只认识主文档里的元素如果你要找的元素被嵌在iframe里必须先driver.switch_to.frame()切进去操作完再switch_to.default_content()切回来。很多新手在这里卡半天报错信息永远是NoSuchElementException还以为是自己定位符写错了。第三个是新窗口和Tab标签页。点击链接打开新窗口后驱动还在老页面上必须先driver.switch_to.window(driver.window_handles[-1])切过去。这里有个挺坑的地方新窗口没加载完之前window_handles可能只有1个需要写一个等句柄数量变化的循环。第四个是元素被遮挡。页面上有弹窗、浮层、遮罩元素本身在DOM里也可见但被其他元素挡住点击就会报ElementClickInterceptedException。处理思路是区分场景如果有弹窗先关掉弹窗如果是遮罩用JS点击driver.execute_script(arguments[0].click();, element)绕过遮挡。但不要一遇到点击失败就无脑JS点击你要明白为什么点击被拦截才能从根上消除问题。第五个是页面异步渲染慢。现在的前端基本都是SPA应用数据是异步加载的元素存在不等于内容填充完成。比如表格的行已经渲染出来了但单元格的文本还是空的你需要等文本不为空再执行下一步。这种情况用EC.text_to_be_present_in_element(locator, expected_text)做等待条件比单纯等元素存在可靠得多。4.2 驱动和浏览器版本不一致的经典报错这个报错信息几乎是每个Selenium用户都见过的SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version X原因就是我前面说的驱动主版本和浏览器主版本对不上。处理方法就两种思路要么把驱动换成本地浏览器对应的版本要么把浏览器换成驱动对应的版本。通常我是先把浏览器更新到最新稳定版然后下载对应版本的驱动这样能保证一段时间内版本不会落后。补充一个查看驱动版本的办法chromedriver --version输出类似ChromeDriver 118.0.5993.70 (1b8b1f1e4f0f8fdb3e6a5d5a2d5c4b2e24b3f7f-refs/branch-heads/5993{#1390})开头就是版本号和浏览器主版本一比就知道是否匹配。顺带提醒macOS上如果下载了驱动但系统提示“无法验证开发者”需要到“系统设置 - 隐私与安全性”里允许该应用运行或者去属性里右键打开一次。这跟Selenium本身没关系但经常被误以为是框架问题。4.3 用例执行顺序与数据隔离问题pytest默认按文件内定义的顺序执行但框架层面不应当依赖执行顺序。我见过很多团队为了省事把用例写成了“上一步的登录状态带到下一步”比如第一个用例登录第二个用例默认已经登录了。这个思路在测试执行稳定期看着挺爽但一旦跑失败后面所有用例全部遭殃而且报错互相干扰根本定位不到真正的问题。解决办法是每个用例都完成独立的准备和清理fixture里处理登录态和测试数据清理。登录操作可以提取成fixturepytest.fixture def logged_in_driver(driver): 已登录的driver login_page LoginPage(driver) login_page.login(test_user, test_pass) return driver用例需要登录就写def test_something(logged_in_driver):不需要登录就直接传driver。这样可读性、可维护性都更强。数据隔离方面我的经验是测试完要清理脏数据。比如往购物车加商品用例结束后要么数量恢复原状要么该测试账号就是专用测试账号允许数据变化。不要跑到别人的共享账号上否则你加的购物车数据会影响其他同事的用例执行。4.4 稳定性优化与执行速度平衡框架搭完能跑只是第一步能稳定跑才是真正的考验。稳定性优化我最看重的几点弃用time.sleep固定等待、慎用class属性定位、减少不必要的前置步骤。time.sleep(5)这种写法看起来简单实则是毒药。页面快的时候白等5秒页面慢的时候5秒还不够时间长了用例执行时间暴涨还会偶发失败。我在代码审查里看到sleep一般都会建议换成WebDriverWait。前置步骤优化方面如果我要测购物车功能但每个用例都必须从登录开始那这些登录动作就是100%重复的成本。中间件和fixture可以把登录提前到session级别多个用例共用一个登录态。前提是业务允许共享登录态并且测试账号不在多个线程间同时使用。如果环境有并发执行的需求可考虑每台执行机使用独立测试账号。执行速度上无头模式headless能省非常多资源。Chrome从v112之后默认是--headlessnew。无头模式的问题在于有些前端兼容性问题只在有头模式下才暴露所以我的建议是本地调试用有头模式CI流水线用无头模式不同场景不同配置。还有一个容易被忽略的点浏览器窗口大小。很多页面做了响应式布局窗口太小的时候某些按钮被折叠进菜单里直接点会找不到元素。fixture里加了--start-maximized就是规避这个问题。如果你要执行移动端适配的页面那又另当别论需要显式设置移动端的窗口尺寸。4.5 日志与失败排查让问题可追踪自动化测试跑挂多少次不重要重要的是挂的时候能不能快速定位。我框架里的日志配置至少包括用例开始结束、每个关键步骤操作、元素定位失败时的页面URL和截图。每个页面类继承了BasePage日志就通过logging统一输出到文件和控制台。更实用的手段是给driver加一层动态代理测试执行时的每个Selenium调用都记录成日志。这样做的好处是遇到偶发失败你能从日志里看到最后一次操作到底是什么是点击还是输入目标元素是什么然后结合截图判断当时的页面状态。Selenium有EventFiringWebDriver可以监听这些事件也是官方支持的方式。框架搭好之后建议花半天时间把这个加上排查问题效率至少提升一倍。5. 框架的进一步扩展接口与UI的协同5.1 为什么要同时考虑接口自动化UI自动化跑得多的人都有体会页面小改动用例就挂一大片数据准备也麻烦全靠点点点效率太低。所以现在比较成熟的项目都会采用UI 接口混合的策略接口负责数据准备、验证核心逻辑UI只负责验证页面交互和最终呈现效果。比如下单这个场景接口层先调用创建订单的接口准备数据UI层只需要验证订单列表正确显示了这条订单、状态是待支付、金额正确。这样用例的稳定性和执行速度都大幅提升。框架里预留好utils/api_client.py模块封装requests调用测试用例可以直接调用接口准备数据不用绕一大堆页面操作。5.2 pytest插件与自定义pytest.inipytest的灵活性很大一部分来自插件和配置文件。项目根目录放一个pytest.ini把常用的命令参数预先配置好团队里所有人执行时都有一致的行为[pytest] addopts -v -s --htmlreports/report.html --self-contained-html testpaths testcases markers cart: 购物车相关用例 login: 登录相关用例 smoke: 冒烟测试用例 regression: 回归测试用例markers的作用在于给用例打标签。冒烟测试只跑-m smoke回归测试跑-m regression发布前跑完整集。这种按标签组织用例的方式比按文件组织更灵活。前面用例里我打了pytest.mark.cart用pytest -m cart就能只跑购物车模块用例。5.3 持续集成接入建议框架最终要部署到CI或者定时任务里才能真正发挥价值。接入CI的思路很简单代码推送到仓库后流水线拉取代码 - 安装依赖 - 启动被测前端服务 - 执行pytest - 上传报告 - 发送通知。在容器或CI机器上执行的关键是处理无头模式和驱动安装。Docker方式一般用独立的selenium/standalone-chrome镜像或者自研镜像里内置Chrome和ChromeDriver。比较常见的坑是CI机器上Chrome版本和本地不一致导致驱动匹配失败。这个环节一定要在Pipeline脚本里写清楚驱动安装的逻辑比如根据Chrome版本动态下载对应驱动。我自己的经验是CI跑自动化测试最好不要在每轮构建都跑全量用例那样又慢又容易误报。更合理的方案是pull request阶段跑冒烟集5-10分钟合并到主干后跑全量回归。给执行做一个分层的概念比一股脑全跑要实用得多。5.4 团队协作时的一致性保障单一团队用同一套框架时最容易出现的问题就是“每个人有自己的写法”。有人把元素定位直接写在用例里有人不用BasePage有人绕过fixture自己启动driver。框架搭建完只是开始真正难的是维护代码风格的一致性。我的做法是在项目里放一个简短的约定文档说明哪些能写、哪些不能写。比如所有元素定位必须写在页面类顶部不允许散落在用例方法里所有等待必须使用WebDriverWait不允许使用time.sleep每个用例必须声明独立依赖不允许依赖执行顺序失败排查优先看日志和截图不允许无头乱改定位符这份约定不用长够用就行。自动化测试框架的很多坑本质上是团队协作的坑规范先行能把很多问题扼杀在萌芽里。写在最后的几点心得框架搭起来只是第一步让它稳定运行、适合团队、容易维护才是长期的修行。回看这些年的自动化测试工作我最大的感受是框架的设计永远要为“易排查”和“易修改”服务而不是为了炫技或者把代码精简到极致。简洁清晰的代码在测试领域比花哨的技巧有价值得多。最后分享一个方法论如果你团队里没有人愿意维护这套框架那自动化测试做得再漂亮也只是技术demo。框架要让维护它的人觉得顺手让使用者觉得省心这比任何技术选型都重要。我见过太多功能完备的框架因为“太重”而被团队放弃也见过很朴素的框架因为简单直接而被长期使用。所以搭框架时多问一句这个设计是真的需要还是只为了显得专业这个问题的答案往往能帮你避开很多自娱自乐的设计。