从功能用例到自动化框架:用千问完成登录模块测试设计与Selenium+pytest落地

📅 发布时间:2026/9/24 23:48:16
从功能用例到自动化框架:用千问完成登录模块测试设计与Selenium+pytest落地
第一次用千问写测试用例是在一个后台管理系统的回归测试排期里。项目要做登录模块和用户管理模块的回归手工用例堆了快两百条还要同步补自动化脚本时间压得很紧。我抱着“让AI先出个初稿我只管改”的心态把需求丢给了千问结果这一试就从功能测试用例一路折腾到了PythonSeleniumpytestPOM数据驱动的自动化测试框架。整条链路跑通之后回头看这次尝试最值的地方不是省了多少时间而是让“功能用例”和“自动化用例”这两套原本各写各的东西第一次在同一个工作流里对齐了。这篇记录适合谁看如果你也在用或打算用AI辅助测试工作如果你正准备把手里的Selenium脚本整理成规范一点的框架或者你只是想看看pytest的数据驱动到底怎么落地这篇都能给你一些参考。里面我不会只贴代码还会说清楚每一步为什么这么做、以及哪些地方AI帮不上忙只能靠人拍板。1. 从“问AI要几条用例”到“干脆搭个框架”这件事的起因先把当时的背景交代清楚。这个后台管理系统用的是经典的账号密码图形验证码登录业务上有个特殊规则员工账号首次登录必须强制改密连续输错5次会锁定30分钟管理员可以手动解锁。这些规则在需求文档里都有但历史原因测试用例文档里一直没有完整覆盖。我一开始给千问的诉求很简单帮我列登录模块的功能测试用例。当时我的想法是网上关于登录用例的文章一抓一大把AI训练语料里肯定不少让它先出个通用清单我再对照业务规则删改比从零开始写效率高。千问第一版返回的用例有40多条分成了正常流程、异常流程、边界情况、安全情况四大类。说实话覆盖面比我预想的广账号空格、密码大小写、验证码刷新、弱密码校验、SQL注入、XSS注入、连续失败锁定、登录跳转、记住密码这些都有。那一刻我的第一反应是“早知道早点用AI”。但紧接着我就发现了问题。AI写的用例有两条明显短板一是它把所有系统当成同一个系统很多用例在我这个项目里根本不存在比如“第三方账号登录”“短信验证码登录”我们压根没这功能二是它完全不知道业务规则比如“首次登录强制改密”这种核心场景40多条用例里一条都没有。所以第一版用例我只能当成素材库真正的用例清单是在这个素材库基础上重新剪裁出来的。也正因为这次体验我意识到一个更值得做的事既然AI能快速产出功能用例那它能不能基于同样的逻辑帮我生成自动化脚本功能用例和自动化用例分开维护一直是我们团队的痛点——功能用例有几百条自动化脚本只覆盖了其中一小部分两边对不上。如果能让AI从功能用例直接带出脚本框架哪怕只是个半成品也能省不少事。于是就有了后面这套PythonSeleniumpytestPOM数据驱动的自动化框架。严格说技术栈不是我拍脑袋定的是我把团队现状、项目特点、自动化覆盖目标一起丢给千问让它给出推荐方案我再结合自己的判断做取舍。这个过程里AI给的不是标准答案而是帮我把选项摆得更清楚。1.1 为什么第一次就敢让AI参与核心工作流这里插一句大家可能关心的问题让AI参与测试用例设计甚至参与框架代码生成会不会有代码安全风险我的做法是往AI里发的都是脱敏过的需求和结构片段不涉及真实密码、不会把生产环境信息贴上去。页面元素定位符、页面结构这些属于项目前端代码的通用特征风险相对可控。如果你所在企业对数据外发有明确限制建议先确认合规要求再决定能不能走这条路。另外一个现实问题是团队里不少新人第一次搭自动化环境就被python安装、selenium插件安装卡住。我也让千问整理了一份环境搭建清单从装Python解释器到配置PATH、再到用pip安装selenium和pytest一步步列出来。这东西本身不复杂但作为第一次搭建的参考帮我省了不少答疑时间。2. 功能测试用例千问的产出与我的三轮人工修订想得到高质量的AI输出提问方式比AI本身更重要。我第一次提问比较随意结果返回的用例太泛什么“验证用户界面美观”“检查页面响应速度”这种没法直接落地的条目都出来了。后来我把提示词改成了带业务规则和格式约束的结构化描述输出质量才明显提升。2.1 我给千问的提示词当时用的是这样一段描述我正在测试一个后台管理系统的登录模块技术栈是B/S架构登录方式为账号密码图形验证码。 业务规则如下 - 账号错误或密码错误时提示账号或密码错误 - 连续输错5次密码账号锁定30分钟 - 首次登录要求强制修改密码 - 修改密码时新密码不能与历史3次密码相同 - 验证码4位数字点击可刷新5分钟后过期 请基于以上规则设计功能测试用例分为正常流程、异常流程、边界情况、安全情况四类 每条用例包含用例编号、用例标题、前置条件、测试步骤、预期结果、优先级。加入业务规则和格式要求之后输出质量明显提升。这个提示词的价值在于它把系统行为约束住了AI不会再天马行空给你设计出“微信扫码登录”这种用例。这里有个细节我特意说了“B/S架构”和“图形验证码”因为这两条直接决定了后面自动化方案里要不要处理验证码、用什么方式驱动浏览器提前告诉AI它给的用例会更贴近可执行层面。2.2 AI返回的核心用例AI生成的条目经过我整理之后大致是这个形态用例编号用例标题前置条件测试步骤预期结果优先级TC-LOGIN-001正确账号密码登录成功已存在有效账号输入正确账号密码点击登录进入系统首页跳转至默认页面P0TC-LOGIN-003账号为空登录无账号留空输入密码点击登录提示“请输入账号”不发起登录请求P1TC-LOGIN-007密码连续输错5次账号锁定账号未被锁定连输5次错误密码第5次提示账号已锁定锁定30分钟P0TC-LOGIN-012首次登录强制改密新创建员工账号首次登录成功后进入修改密码页修改完成前无法使用其他功能P0TC-LOGIN-018验证码过期页面停留超过5分钟输入账号密码提交过期验证码提示验证码已过期点击验证码图片可刷新P1TC-LOGIN-023密码框输入SQL注入字符串无密码框输入 OR 11提示账号或密码错误无异常数据返回P1当然AI不会直接输出这么整齐的表格格式是我统一过的。但每条用例的业务来源确实来自AI生成内容我只是做了去重、归类和字段规范。这个工作量比从零写小得多大概只花了三分之一的时间。2.3 三轮人工修订都改了什么第一轮是删。删掉不适用于本项目的用例比如第三方登录、验证码自动识别这类不存在功能同时把“页面加载是否美观”“操作是否流畅”这种不可量化条目直接打回。第二轮是精确化前置条件和预期结果。AI写预期结果经常是“提示密码错误”但没说错误提示文案具体是什么我全部改成了和需求文档一致的文案。第三轮是标优先级并对齐自动化覆盖矩阵P0用例必须进自动化P1争取自动化P2暂不覆盖。这里有一个很重要的心得。AI生成的用例价值恰恰在“它不知道业务规则”这件事上。因为它不知道它会按通用逻辑把所有可能性列出来这能帮我把那些多年惯性思维下漏掉的边界点找出来。比如我做了几年登录模块测试从来不测“密码为全空格”这种极端情况AI列出来了我实际一测系统确实对全空格密码做了特殊处理而需求文档里根本没写这就是一个真实的业务逻辑缺陷。所以AI写用例的正确用法不是让它代替你做判断而是让它帮你查漏补缺。3. 自动化框架选型Selenium、pytest、POM、数据驱动各自的角色功能用例审完开始考虑自动化。在选择技术栈之前我先把功能用例里适合自动化的筛了一遍回归频率高、步骤固定、预期结果明确的用例才值得自动化。像登录、用户创建、角色权限分配这种完全适合而涉及UI复杂交互的、需要人工判断视觉效果的自动化性价比就低。这一步做完有30条用例进入自动化范围登录模块占了一大半。3.1 为什么还是选Selenium团队里之前有过一套零散的Selenium脚本用的是unittest脚本和用例逻辑混在一起页面元素定位散落在各个函数里改一个按钮的id要全局搜索替换。所以这次团队一度想换Playwright。我把这个问题拿去问千问它的回答比较理性如果团队已有Selenium积累、浏览器环境兼容要求高、项目短期内没有跨浏览器并行强需求继续用Selenium是稳妥选择Playwright的优势主要在自动等待、多浏览器支持、拦截网络请求这些能力上但对团队有学习成本。最终我们从成本和稳定出发保留了Selenium但把代码结构完全重写。这里我想多说一句。技术选型最怕的不是选错而是反复横跳。Selenium在当前这个项目的定位就是“稳定够用”它确实不像Playwright那样自带一堆现代特性但它的社区语料足够多AI对它的理解也最深这意味着遇到任何问题你都能快速得到靠谱的参考实现。对测试团队来说可维护性比炫技重要得多。3.2 pytest胜在fixture和参数化测试框架从unittest迁到pytest是这次最大的结构变化。pytest的fixture机制帮我解决了最头疼的两个问题一是浏览器driver的创建和销毁用session级别的fixture做一次启动整个测试类共享二是每次用例执行前要不要重新登录用autouse的fixture控制。参数化更是天然适合数据驱动后面章节会具体说。我给千问的选择题是unittest还是pytest它的回答很干脆pytest。理由包括fixture比setUp/tearDown灵活、conftest.py做共享配置、参数化比ddt简洁、第三方插件生态丰富。这些判断我都是认同的。真正常用的pytest插件其实就三四个pytest-html或allure-pytest生成测试报告、pytest-xdist做分布式执行、pytest-rerunfailures做失败重跑、pytest-ordering控制极端场景下的执行顺序。3.3 POM模式的价值一张图说不清但代码能说明POMPage Object Model不是新技术它的核心思想一句话就能讲清楚一个页面一个类页面上的元素定位和操作方法都封装在类里测试用例只负责编排业务动作和断言结果。这样做的好处是页面改了只改对应页面类用例里永远不会出现driver.find_element这种裸调用可读性和可维护性都会上一个台阶。从AI的视角看它理解POM的速度很快甚至能直接给出BasePage、LoginPage的代码骨架。因为它的训练语料里有大量这类项目所以这部分生成质量挺高我只需要根据实际页面结构调整。这一点也是我建议大家在用AI写代码时优先选它做“脚手架”的原因——通用设计模式AI最擅长业务特化逻辑才是你需要亲力亲为的部分。3.4 数据驱动到底解决了什么问题数据驱动的本质是把“输入数据和预期结果”从测试脚本里拿出去放到Excel、JSON或YAML这类外部文件里。测试脚本变成一套固定的执行逻辑根据不同的数据组合反复执行。当时做这个决定核心原因是登录用例有大量“用户名密码预期结果”的组合如果每种组合都写成一条用例函数代码会有一两百行重复用数据驱动代码只需要一个参数化函数数据维护全在Excel里。这个设计带来的收益是实打实的新增一条用例不需要动代码业务测试人员也能维护数据文件。更重要的是前面辛辛苦苦整理的功能测试用例表终于可以直接作为自动化数据源了。功能用例和自动化用例在Excel里共用一条记录这就是标题里“功能测试用例与自动化测试用例”两套东西对上的关键点。4. POM落地登录模块从功能用例到页面对象的翻译全过程选型定完开始写代码。这个阶段我用千问生成了一版骨架然后按照项目的真实页面结构调整。整体没什么惊险但这个“翻译”过程值得记录它正好展示了功能用例是怎么一步步变成自动化脚本的。4.1 项目结构设计目录结构是这样的project/ ├── config/ │ └── settings.py # 全局配置URL、超时时间、浏览器类型 ├── data/ │ ├── login_data.xlsx # 测试数据文件 │ └── login_data.json # 备用数据文件 ├── pages/ │ ├── base_page.py # 页面对象基类 │ └── login_page.py # 登录页面对象 ├── test_cases/ │ ├── conftest.py # fixture和钩子 │ └── test_login.py # 登录模块测试用例 ├── utils/ │ ├── excel_reader.py # Excel读取工具 │ └── log_utils.py # 日志工具 ├── reports/ # 测试报告输出目录 └── requirements.txt这个结构不是我凭空想的我把需求丢给千问它给了一版几乎相同的结构我只加了config目录。目录分层的逻辑很简单数据归数据、页面归页面、用例归用例、工具归工具各层之间单向依赖不允许用例直接操作浏览器API。这样新人接手时看一眼目录就明白什么东西该放哪。4.2 BasePage封装把Selenium的原始操作包成业务动作BasePage是所有页面对象的父类封装Selenium最常用的操作。我最初版本几乎照搬了千问给的内容后来又加了失败截图和日志变成下面这样# pages/base_page.py import time import logging from pathlib import Path from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC 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.visibility_of_element_located(locator)) def find_clickable_element(self, locator): 查找可点击元素用于按钮、链接等 return self.wait.until(EC.element_to_be_clickable(locator)) def input_text(self, locator, text): 输入文本每次输入前先清空 elem self.find_element(locator) elem.clear() elem.send_keys(text) logger.info(f向 {locator} 输入文本: {text}) def click(self, locator): 点击元素 elem self.find_clickable_element(locator) elem.click() logger.info(f点击元素: {locator}) def get_text(self, locator): 获取元素文本 return self.find_element(locator).text def take_screenshot(self, namescreenshot): 失败时截图文件保存在 reports/screenshots/ 下 timestamp time.strftime(%Y%m%d_%H%M%S) report_dir Path(__file__).parent.parent / reports / screenshots report_dir.mkdir(parentsTrue, exist_okTrue) filepath report_dir / f{name}_{timestamp}.png self.driver.save_screenshot(str(filepath)) logger.info(f截图保存至: {filepath})这里有两个容易踩坑的细节。第一个find_element用了显式等待而不是Selenium默认的隐式等待。隐式等待是轮询整个driver的显式等待只作用于特定元素两者混用会出现等待时间叠加导致用例无故变慢。第二个input_text里必须先clear()再send_keys因为不少输入框有默认值或上次会话残留不清理直接输入结果会跟你预期完全不一样。4.3 LoginPage把功能用例中的“步骤”变成“方法”LoginPage对应登录页把功能用例里的“输入账号”“输入密码”“点击登录”“获取错误提示”这些步骤全部变成方法。元素定位符集中定义在类顶部的元组里这是POM最重要的写法约定修改定位符只需要改一处不需要去脚本里满世界找。# 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) captcha_input (By.ID, captcha) login_button (By.ID, loginBtn) error_tip (By.CLASS_NAME, error-tip) captcha_image (By.ID, captchaImg) def input_username(self, username): self.input_text(self.username_input, username) def input_password(self, password): self.input_text(self.password_input, password) def input_captcha(self, captcha): self.input_text(self.captcha_input, captcha) def click_login(self): self.click(self.login_button) def get_error_tip(self): return self.get_text(self.error_tip) def login(self, username, password, captcha0000): self.input_username(username) self.input_password(password) self.input_captcha(captcha) self.click_login()这段代码看起来简单但它是功能用例到自动化脚本的关键桥梁。功能用例写的是“输入正确账号密码点击登录”对应到这里就是login_page.login(admin, 123456)。用例层读起来像自然语言每个方法只做一件事。尤其是login()这个组合方法在后面所有的登录用例里都会被复用这也是POM减少重复代码的典型体现。我特意在login()里给验证码设置了一个默认值“0000”。这是测试环境专门配的万能验证码后面讲验证码坑的时候会展开。这里想强调的是代码里出现这种环境相关的魔法值时一定要用默认参数和注释说明原因否则三个月后你自己都忘了“0000”是什么东西。4.4 conftest.pyfixture是pytest的灵魂conftest.py放的是整个测试目录共享的fixture。浏览器启动、页面对象初始化、失败处理全都在这里集中管理。# test_cases/conftest.py import pytest from selenium import webdriver from pages.login_page import LoginPage pytest.fixture(scopesession) def driver(): 启动浏览器整个测试会话只执行一次 options webdriver.ChromeOptions() options.add_argument(--start-maximized) # 无头模式在CI环境使用本地调试注释掉 # options.add_argument(--headlessnew) driver webdriver.Chrome(optionsoptions) driver.implicitly_wait(2) yield driver driver.quit() pytest.fixture() def login_page(driver): 每个用例独立获取一个登录页面对象 page LoginPage(driver) driver.get(http://your-test-env.com/login) return page pytest.hookimpl(tryfirstTrue, 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: driver.save_screenshot(freports/screenshots/{item.name}.png)fixture的作用范围scope需要仔细设计。driver用session级别是为了避免每个用例都重新启动浏览器否则30条用例至少多花10分钟。但这也带来一个代价用例之间共享同一个浏览器会话一个用例改了登录状态会影响后面用例。所以login_page这个fixture故意不设scopesession让每条用例都重新访问登录页确保起始状态干净。4.5 用例层像读功能用例一样读自动化脚本有了页面对象和fixture用例层的代码就非常干净了# test_cases/test_login.py import pytest class TestLogin: def test_login_success(self, login_page): TC-LOGIN-001 正确账号密码登录成功 login_page.login(admin, 123456) assert login_page.driver.current_url.endswith(/dashboard) def test_login_wrong_password(self, login_page): TC-LOGIN-005 密码错误时提示账号或密码错误 login_page.login(admin, wrongpass) assert login_page.get_error_tip() 账号或密码错误 def test_login_account_locked(self, login_page): TC-LOGIN-007 密码连续输错5次账号锁定 for _ in range(5): login_page.login(admin, wrongpass) assert login_page.get_error_tip() 账号已锁定请30分钟后再试这里要注意test_login_success里需要重新登录所以即使前一条用例已经登录成功这条用例也通过login_page这个fixture重新回到了登录页。这正是fixture设计时埋的伏笔每个用例都从登录页开始互不干扰。看到这里你会发现功能用例里的“测试步骤”和“预期结果”在自动化里变成了“调用页面对象的方法”和“断言”。这就是标题里“功能测试用例与自动化测试用例”的翻译过程。AI在中间的贡献主要是把通用结构搭好但每一条业务断言的具体值比如错误提示文案必须人工核对需求文档。5. 数据驱动改造测试数据搬进Excel之后发生了什么前面代码里的用例数据都是写死的login_page.login(admin, 123456)这种形式对固定测试场景没问题但要覆盖前面整理的那30条功能用例就得写30个类似的测试函数显然不现实。数据驱动就是来解决这个问题的。5.1 数据文件怎么设计我用Excel作为数据载体结构如下用例编号用例标题用户名密码验证码预期结果是否执行优先级TC-LOGIN-001正确账号密码登录成功admin1234560000dashboardYP0TC-LOGIN-003账号为空空1234560000请输入账号YP1TC-LOGIN-005密码错误adminwrongpass0000账号或密码错误YP0TC-LOGIN-007连续输错5次锁定adminwrongpass0000账号已锁定YP0TC-LOGIN-012首次登录强制改密newuserinit1230000修改密码页YP0TC-LOGIN-018验证码过期admin123456expired验证码已过期NP1为什么用Excel而不是JSON两个原因。第一业务测试人员熟悉Excel让他们维护测试数据基本零学习成本第二Excel可以很直观地做筛选、排序、标记是否执行这在JSON和YAML里都没这么顺手。当然Excel在代码里读取时需要额外装openpyxl库这是个小成本换来的是业务人员能直接参与用例维护。5.2 读取Excel的工具方法# utils/excel_reader.py import openpyxl from pathlib import Path def read_excel_rows(filepath, sheet_nameNone): 读取Excel每一行返回字典列表 filepath Path(filepath) wb openpyxl.load_workbook(filepath, data_onlyTrue) ws wb[sheet_name] if sheet_name else wb.active rows list(ws.iter_rows(values_onlyTrue)) if not rows: return [] headers [str(h).strip() if h else for h in rows[0]] result [] for row in rows[1:]: item dict(zip(headers, row)) # 跳过标记为 N 的用例 if str(item.get(是否执行, Y)).strip().upper() N: continue result.append(item) return result这段代码有几个细节值得提。第一个data_onlyTrue是为了读取Excel里公式计算后的值而不是公式本身不然你读到的可能是CONCATENATE(...)而不是结果。第二个表头做了一次strip()Excel里单元格经常带空格不清理会让字典的key和后续代码对不上。第三个“是否执行”字段的过滤逻辑放在读取工具里而不是用例层这样所有数据统一遵守这个规则不会有的用例读进来又不执行。5.3 pytest参数化绑定数据文件有了下一步就是把它和pytest的参数化机制绑定# test_cases/test_login.py import pytest from utils.excel_reader import read_excel_rows LOGIN_DATA read_excel_rows(data/login_data.xlsx) class TestLogin: pytest.mark.parametrize( case, LOGIN_DATA, ids[f{case[用例编号]}-{case[用例标题]} for case in LOGIN_DATA] ) def test_login_with_data(self, case, login_page): username case[用户名] password case[密码] expected case[预期结果] login_page.login(username if username ! 空 else , password) current_url login_page.driver.current_url if expected dashboard: assert current_url.endswith(/dashboard) elif expected 修改密码页: assert current_url.endswith(/change-password) else: assert login_page.get_error_tip() expected这里的ids参数是容易被忽略但很重要的一点。没有它pytest会把每条参数化用例命名为test_login_with_data[case0]、test_login_with_data[case1]报告里完全看不出哪条用例是干什么的。加上ids之后报告里显示的是test_login_with_data[TC-LOGIN-001-正确账号密码登录成功]一目了然。这个测试函数里有一个明显的“业务翻译”逻辑把数据文件里的“预期结果”转换成断言动作。比如dashboard代表断言当前URL以/dashboard结尾修改密码页代表断言URL以/change-password结尾其他文本则断言页面上的错误提示。这种转换逻辑放在用例层是合理的因为数据文件要保持人能直接看懂的描述而不是塞一堆current_url.endswith这样的技术细节。5.4 数据驱动之后的用例管理数据驱动落地之后最直接的变化是新增一条用例不需要写代码只需要在Excel里加一行填好编号、标题、数据、预期结果和执行标记。跑测试的时候框架会自动读取所有标记为“Y”的用例并执行报告里自动带上用例标题。这也是让业务测试人员参与自动化维护的前提——他们不需要理解pytest只需要管Excel。这里我也想提醒一句数据驱动不是万能的。它适合参数组合较多、断言逻辑相对统一的场景登录、查询、表单提交都适合。但如果用例的步骤差异很大比如一条用例是登录另一条是上传文件还带复杂的交互断言强行塞进同一个参数化函数里反而会让代码里塞满if-else分支。这时候老老实实拆成多个测试函数更清晰。我划分的标准是断言逻辑是否一致而不是数据是否相似。6. 首次全流程执行超时、验证码、数据污染三个坑挨个排框架写完真正全流程跑的时候问题一个接一个。这是整个过程中最有价值的一段因为这些问题你在任何教程里都看不到完整的解决方案但几乎每个跑Selenium数据驱动的人都会遇到。6.1 浏览器driver版本不匹配第一跑就挂在环境上。本地浏览器是Chrome 120但代码里selenium自动拉的driver版本不是对应的直接报SessionNotCreatedException。如果你是用selenium 4以上版本它带了Selenium Manager理论上能自动匹配driver但公司内网环境往往访问不了driver下载地址这时候只能手动下载对应版本的chromedriver放到PATH目录下。处理方法不难先看本地Chrome版本chrome://version里能看再去对应driver镜像站下载同大版本的driver放到/usr/local/bin或Windows的Scripts目录重新跑。这一步顺便排查了团队里新人的环境问题——很多人卡在“python安装好了但selenium跑不起来”十有八九都是driver版本和浏览器版本对不上。6.2 元素定位超时显式等待的细节环境通了之后下一个遇到的是点击登录按钮时报ElementClickInterceptedException。原因不是定位符写错而是页面上有个弹层遮住了登录按钮Selenium虽然能定位到元素但元素被遮挡无法点击。这个场景在登录页很典型一些系统登录前会先弹一个公告或者通知层必须点掉才能操作。我在BasePage里已经封装了find_clickable_element它用element_to_be_clickable等待这个条件不只是检查元素存在还会检查元素可见且可点击。但它没法自动帮你关弹层。最后的解法是在登录页对象里加一个关闭公告弹层的方法在login()里先尝试关闭def close_announcement_if_present(self): 关闭登录页可能出现的公告弹层 try: close_btn (By.CLASS_NAME, announce-close) self.wait.until(EC.visibility_of_element_located(close_btn)).click() logger.info(公告弹层已关闭) except TimeoutException: pass # 没有弹层忽略用try-except包住TimeoutException是关键因为公告弹层不是每次都出现。这种“可能出现的元素”在自动化里很常见处理原则是能容忍的异常就捕获并处理不要让它中断主流程。6.3 验证码是个躲不开的坎登录页有图形验证码这在自动化里是个经典问题。AI给出的方案有好几个OCR识别、对接打码平台、通过请求直接获取验证码值。但对内网测试系统来说这些方案都太重了。最合理的做法是让开发在测试环境提供一个固定的万能验证码比如“0000”所有自动化用例统一填这个值。如果测试环境实在改不了验证码逻辑还有一个相对稳的做法登录接口单独开一个白名单自动化的请求跳过验证码校验。但这需要开发配合而且白名单本身有安全风险生产环境绝对不能开。总之验证码问题的核心不是技术而是沟通——尽早和开发约定测试环境的验证码策略能省下大量时间。6.4 数据污染用例之间互相影响的教训跑数据驱动用例时发现TC-LOGIN-012“首次登录强制改密”执行之后后面的登录用例开始失败。原因很直接这条用例用账号newuser首次登录后系统强制跳到了改密页newuser的密码已经被修改后续用例如果再用newuser旧密码登录当然会失败。这就是典型的数据污染。解决办法有两个方向。一是在用例前置里做数据清理确保每个账号的初始状态已知二是给每个用例使用独立的测试数据比如用随机后缀创建新账号。对于这个登录场景我最终选择了前置数据处理在执行强制改密用例前通过数据库或接口把newuser的密码重置回初始状态。另外执行顺序也有讲究我在pytest里用pytest.mark.run(order1)把这条用例固定在前面避免它影响其他数据。6.5 失败重跑和报告配置最后是稳定性的问题。30条用例第一次跑下来有3条失败但单独重跑这3条又全过。这属于典型的偶发失败多半是网络延迟或元素加载慢。我给关键用例加了pytest.mark.flaky(reruns2)重跑两次如果再失败才判失败。pytest-rerunfailures这个插件很轻量用法就是这么一行。报告我用的是pytest-html配置很简单在pytest.ini里加几行[pytest] addopts -v --htmlreports/report.html --self-contained-html testpaths test_cases--self-contained-html这个参数很重要它会把CSS和JS都打进HTML文件里报告可以直接发给别人不用附一堆静态资源。配合前面conftest.py里的失败截图钩子报告里能看到每一条失败用例当时页面的样子排错效率高了一大截。7. 用AI写测试用例这件事我的真实体会整个流程走完我对“AI辅助测试”这件事的认知变了很多。它不是简单的“让AI替你写用例”更准确的说法是“让AI做初稿人做判断”。AI最强的能力是用它海量的语料积累把我可能忽略的边界情况、通用场景、代码结构一次性摆到桌面上来但AI不具备任何关于你这个具体系统的业务知识也不知道你们团队历史上踩过哪些坑。这两个特点决定了它适合做助手不适合做决策者。具体说AI在这些环节真正的帮助最大生成功能用例初稿、搭POM框架的脚手架、解释Selenium某个报错的含义、把日志里的异常翻译成人话。而在判断业务规则对不对、决定某种数据组合是否合理、区分哪些用例必须P0优先级这些地方AI给不了任何有效建议只能靠测试人员基于对项目的理解来拍板。提示词的使用也有几个心得。第一给上下文比不给上下文好得多把业务规则、技术栈、格式要求一次性写清楚AI的输出质量会高一个档次。第二逐步追问比一次性问完更有效我会先让它列功能用例再让它从中挑适合自动化的再让它按POM结构生成代码每一步都在上一步基础上深化。第三让它输出结构化内容表格、代码、列表比让它写长文更实用方便我直接复制到文档和工程里。回到我一开始的预期想省时间。这件事AI确实做到了。更让我觉得值得的是功能用例文档、自动化数据、测试报告这三样东西现在完全对齐了。以前功能测一遍、自动化脚本再写一遍两边数据和预期结果经常对不上现在维护同一个Excel跑一次自动化报告直接说明每个功能用例在自动化里的表现。对我来说这才是AI参与测试工作流最大的价值。最后再说一个使用上的小建议AI给的用例和代码一定要亲自跑一遍、逐条看一遍再合入。我第一次跑框架时AI生成的代码里就有几处明显的低级错误比如变量名拼写不一致、循环里漏了递增条件。它写代码的能力再强也替代不了人工审查这一关。把AI当成一个工作效率放大器而不是一个可以完全托付的同事用起来才会顺手。