Python+Selenium Web自动化测试实战:从环境搭建到测试框架设计

发布时间:2026/10/11 6:12:45

Python+Selenium Web自动化测试实战:从环境搭建到测试框架设计 1. 为什么选PythonSelenium做Web自动化测试1.1 Web自动化测试的核心需求做Web测试的人应该都有过这样的经历一个功能改完需要把主流程从头到尾点一遍登录、跳转、填表单、上传文件、验证结果……一遍下来十几分钟一天反复点十来次手指都麻了还不一定每次都点对位置。这时候你最需要的就是一套能帮你“按剧本自动点页面”的工具而PythonSelenium正是目前行业内最常用、最贴近实际需求的组合之一。Selenium的本质是浏览器自动化框架它通过驱动真实浏览器Chrome、Firefox、Edge等模拟用户操作打开页面、点击按钮、输入文本、滚动页面、截图、断言页面内容。和单元测试不同Web自动化测试关心的是“整个系统在真实浏览器里跑起来是否符合预期”所以它适合做回归测试、跨浏览器验证、多轮构建冒烟测试也适合帮你自动完成一些重复性很高的验收流程。我要写的这套思路面向的是手里有Web项目、想引入自动化但还没找到合适切入点的人也适合已经把Selenium跑起来但总被各种诡异报错折磨的初级测试开发。很多朋友误以为自动化测试就是录个脚本回放真这么干你会发现录出来的脚本换个环境就废了。真正的Web自动化测试需要你理解页面的结构、元素的属性、网络请求时机、浏览器渲染节奏还要能处理各种异步加载。Python的语法简单、生态成熟配合Selenium的API可以让我们把重点放在“测什么、怎么判断结果”上而不是花大量时间在环境配置和底层驱动上。这也是为什么绝大多数测试团队的自动化框架都长成“Python Selenium pytest”的样子。1.2 Python与Selenium的组合优势先说Python在这一环里的角色。它是一门上手成本极低的语言写脚本不需要编译改完就能跑。测试脚本往往需要快速迭代页面改了定位符变了脚本就得跟着改Python的灵活性让这类改动变得很轻。再加上它有丰富的断言库unittest、pytest、hamcrest、数据驱动工具pytest参数化、yaml、excel读取和报告生成插件allure、pytest-html能轻松把一个“点页面的脚本”升级成一个“可维护的测试框架”。Selenium的优势更直接它支持所有主流浏览器且API风格统一。你在Chrome上写好的脚本换一个驱动参数就能在Edge或Firefox上跑这就解决了“用户用什么浏览器”的兼容性问题。它还能与DevTools协议对接实现网络拦截、性能日志收集等高级能力。对于测试人员来说Selenium的定位并不是一个“录放工具”而是一套完整的浏览器自动化协议你可以在它之上实现几乎所有和页面交互有关的操作。对比一下其他方案Windows桌面自动化用UiPath更方便接口测试用Requests更轻但Web页面级自动化尤其是需要真实浏览器渲染效果、需要验证JavaScript交互的场景Selenium依然是覆盖最广、资料最多、团队接手成本最低的方案。如果你的项目前端是React、Vue这类框架或者页面大量使用异步加载Selenium配合显式等待依然能稳定覆盖。1.3 环境搭建的关键步骤Python、pip、Selenium与浏览器驱动关于Python安装很多人卡在环境变量上。这里我直接说一个最稳的顺序去Python官网下载安装包安装时一定要勾选“Add Python to PATH”否则后续在命令行里敲python会提示“不是内部或外部命令”。装完之后打开命令行验证一下python --version pip --version能看到版本号就说明环境没问题。接下来安装Selenium库pip install selenium这里有个常见坑如果你在电脑上装了多个Python版本pip可能指向的不是同一个Python解释器。建议在命令行里用python -m pip install selenium这样能确保装到当前python对应的环境里。装完可以用一行代码快速验证python -c from selenium import webdriver; print(ok)能打印ok说明库已经可用。然后是浏览器驱动。Selenium需要驱动程序去和浏览器通信。以Chrome为例你需要一个chromedriver它的版本必须和Chrome浏览器版本匹配。最简单粗暴的方法是手动下载但我更推荐直接装WebDriver Manager它会自动匹配当前浏览器版本并下载对应驱动pip install webdriver-manager然后写脚本的时候这样初始化from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice)这样写完之后不管Chrome怎么更新只要webdriver-manager库保持最新驱动就不会莫名其妙就报SessionNotCreatedException。实际上很多测试环境不稳定的问题都出在驱动版本失配这一条上用这个方案可以省掉一大半烦恼。2. 基础实操从打开浏览器到定位元素2.1 写下第一段Selenium脚本环境就绪后我们直接写一个“打开页面并判断标题”的最小脚本from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.common.by import By service Service(ChromeDriverManager().install()) driver webdriver.Chrome(serviceservice) driver.get(https://www.example.com) assert Example in driver.title driver.quit()这里把四个关键操作串起来了创建驱动、访问URL、做断言、关闭浏览器。driver.quit()非常重要不写的话脚本结束后Chrome进程会残留时间一长内存被占满跑一批用例就卡死了。我习惯用try/finally或者pytest的fixture做清理保证即使断言失败浏览器也会被关闭。冒烟脚本跑通后你需要理解一个核心概念Selenium之所以能操作页面核心在于“元素定位”。浏览器把页面解析成DOM树Selenium根据你提供的定位策略先在DOM里找到那个元素然后调用该元素对应的方法去点击或输入。如果找不到元素就会报NoSuchElementException这也是新手最常碰到的错误。2.2 元素定位的几种方法从id到XPathSelenium里最常用的定位方式有By.ID、By.NAME、By.CLASS_NAME、By.CSS_SELECTOR和By.XPATH。生活化类比一下定位元素就像找人。如果每个人都有身份证号那By.ID就是直接报身份证号查人最快最准。没有身份证号但有姓名By.NAME也行但重名的多。CSS_SELECTOR就像“穿红衣服站在第三排的那个人”通过样式和层级关系描述位置。XPath则是“从大楼门口开始左拐第二个房间里的高个子”用绝对或相对路径来描述元素位置。实际项目里开发者不一定给每个元素都加id我建议定位优先级是这样ID name CSS_SELECTOR XPath。ID有唯一性保障是首选CSS_SELECTOR语法简洁、性能好XPath虽然灵活但表达式写长了可读性差维护成本高能不用复杂的就不用。举个实际场景一个登录按钮HTML是这样button idlogin-btn classbtn-primary登录/button用CSS定位可以写成#login-btn用XPath是//button[idlogin-btn]。两个都能用但前者一眼就懂。如果你的前端框架是Vue或React数据绑定会渲染出很多动态class比如classbtn btn-primary btn-lg这种用class定位反而费劲这时候用By.CSS_SELECTOR配合属性选择器更稳。另外要说一句XPath里的绝对路径/html/body/div[2]/div[1]/form/button非常脆页面一改结构就崩别这么写。要用就用相对路径配合//和[attributevalue]至少能撑过几次改版。2.3 常用交互操作点击、输入、下拉框与等待定位到元素之后就要“跟它交互”。最基础的三板斧element.click()element.send_keys(内容)element.clear()点击和输入没什么好说的唯一要提醒的是send_keys默认是追加输入。如果输入框里之前有默认值请先调用clear()再输入不然会变成“默认值你输入的内容”。下拉框是一个特殊场景。原生HTML的select标签Selenium提供了专门的Select类from selenium.webdriver.support.ui import Select select Select(driver.find_element(By.ID, city)) select.select_by_visible_text(北京)这里注意如果下拉框选项是动态加载的必须先等选项出现再操作否则会报“option not found”。如果是前端自己做的自定义下拉组件不是原生select那就得先点击展开再点击具体的选项本质上和普通元素操作没区别。再说等待。Web页面十有八九有异步请求点击“查询”按钮后数据是后拿到的页面元素不会立刻出现。如果脚本不做任何处理下一步直接找结果表格大概率就报找不到元素。这里有两个等待机制隐式等待driver.implicitly_wait(10)表示WebDriver在查找元素时在没有立即找到的情况下轮询等待最多10秒。它只对查找元素生效。显式等待WebDriverWait是针对某个条件进行等待比如“元素可点击”“元素可见”更精确。我强烈建议你在每个“可能等待”的关键动作前使用显式等待。隐式等待全局生效处理简单页面够用但遇到复杂的条件比如按钮变成可点击状态、元素被重新渲染它兜不住。简单说隐式等待是“等它出现在DOM里”显式等待是“等它变成我想要的状态”。后者才是测试稳定性的基石。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC login_btn WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, login-btn)) ) login_btn.click()这段代码会一直等待“登录”按钮可点击超时则抛异常。相比固定sleep(5)显式等待不会浪费时间也不会因为网络慢而误判失败是实际测试框架里的标配。3. 进阶页面滚动、截图与复杂场景处理3.1 网页左右滑动与滚动条控制的实战细节很多测试场景需要处理滚动尤其是“滚动加载更多”“左右滑动轮播图”“横向滚动表格”这类交互。Selenium没有直接提供“滚动到底部”的API通常用JavaScript执行scroll操作。先看最常用的垂直滚动driver.execute_script(window.scrollTo(0, document.body.scrollHeight))这会把页面滚动到最底部适合触发懒加载内容。对于“滚动到某个元素可见”可以用element driver.find_element(By.ID, target) driver.execute_script(arguments[0].scrollIntoView();, element)scrollIntoView()是一个很实用的原生方法它会让浏览器把元素滚动到可视区域。注意它有两个可选参数scrollIntoView(true)表示元素顶部对齐视口顶部scrollIntoView(false)表示元素底部对齐视口底部默认是true。如果页面有固定头部导航遮挡滚动后元素可能被遮住这时候可以传一个偏移值driver.execute_script(arguments[0].scrollIntoView(); window.scrollBy(0, -80);, element)这段代码先把元素滚到顶部再向上滚动80像素避免被固定导航盖住。再说左右滑动。网页里的横向滚动常见于两种整个body出现横向滚动条或者某个容器内出现横向滚动条比如一组卡片、一个表格。处理整个页面横向滚动可以这样driver.execute_script(window.scrollBy(300, 0))但更常见的是容器内部滚动。比如一个div设置了overflow-x: auto你直接window.scrollBy滚不动因为window没有滚动条。这时候要把滚动作用到那个容器上container driver.find_element(By.CLASS_NAME, scroll-container) driver.execute_script(arguments[0].scrollLeft 500;, container)scrollLeft是可以赋值的属性给它设置一个像素值容器就会横向滚动到对应位置。如果你的目标是“让某个元素在容器内水平居中可见”可以这样算driver.execute_script( arguments[0].scrollIntoView({behavior: instant, block: nearest, inline: center});, element )这里的inline: center会让元素在水平方向上尽量居中。这个方法兼容性不错我在处理卡片轮播和宽表格时经常用。还有一个常用的技巧是模拟鼠标滚轮效果使用ActionChainsfrom selenium.webdriver.common.action_chains import ActionChains action ActionChains(driver) action.move_to_element(element).perform()move_to_element会把鼠标移动到元素上如果元素在当前视口外浏览器会自动滚动到可见位置。这比直接执行JS更接近真实用户行为缺点是滚动位置不可精细控制。我一般把它用在“悬浮触发下拉菜单”的场景而精确滚动用scrollIntoView。3.2 等待机制再深入显式等待条件的组合使用上一节我提了显式等待的基础用法这里展开讲几个实战中常用的expected_conditions条件以及如何组合它们。visibility_of_element_located元素可见且占据页面空间。适用于多数字段、按钮。presence_of_element_located元素在DOM中存在但未必可见。适用于隐藏元素、动态加载后数据的判断。element_to_be_clickable元素可见且可用。适用于按钮、链接。text_to_be_present_in_element元素文本包含指定内容。适用于验证操作结果比如“提交成功”提示。element_to_be_selected适用于单选框、复选框、下拉框的选中判断。实际使用中一个操作可能依赖多个条件。比如点击“保存”后页面先出现Loading遮罩然后出现“保存成功”的Toast最后按钮恢复可点击。如果只等按钮可点击可能在Loading遮罩消失前按钮就是可点击的但点击会被遮罩挡住。这时候应该组合等待wait WebDriverWait(driver, 10) wait.until(EC.invisibility_of_element_located((By.CLASS_NAME, loading-mask))) wait.until(EC.visibility_of_element_located((By.CLASS_NAME, toast-success))) wait.until(EC.element_to_be_clickable((By.ID, save-btn)))这种链式等待看起来啰嗦却是稳定性的关键。不要迷信“等待时间越长越好”等待时间过长会让错误用例拖很久正确做法是给每个等待设定合理的超时上限比如10秒或15秒然后让条件尽量精确。这里分享一个我个人很喜欢的封装。写一个带重试的click方法把常见点击异常统一处理def safe_click(driver, locator, timeout10): try: element WebDriverWait(driver, timeout).until( EC.element_to_be_clickable(locator) ) element.click() return True except Exception as e: # 这里记录日志截图然后决定是重试还是抛出 raise e当然这只是一个骨架你可以根据项目里的反馈把日志和截图加进去。3.3 截图与错误录制让测试报告不再干瘪自动化测试跑挂了最遗憾的事情就是只看到一行堆栈NoSuchElementException: Message: no such element: Unable to locate element。页面当时长什么样完全不知道排查全靠猜。所以截图是自动化测试中绝对不能省的一环。Selenium对当前窗口截图的原生方法是driver.save_screenshot(failure.png)或者用driver.get_screenshot_as_png()返回二进制数据方便写进报告。更进阶的做法是只对某个元素截图比如页面上弹出了错误提示你可以只截那一块element driver.find_element(By.CLASS_NAME, error-message) element.screenshot(element_error.png)在实际框架里我建议把截图埋进pytest的异常处理逻辑中。通过pytest的pytest_runtest_makereport钩子或者最简单的在fixture里处理pytest.fixture def fail_screenshot(): yield if request.node.rep_call.failed: driver.save_screenshot(freport/{request.node.name}.png)除了截图还可以把当时的页面HTML保存下来。用driver.page_source拿到当前DOM字符串写进文件。有些错误是页面渲染完成但内容不符合预期截图看不出来HTML里能搜索关键文本定位到底是前端渲染问题还是后端数据问题。这里提醒一句Selenium的截图是“所见即所得”如果页面在滚动区域之外的内容默认截图里没有。想截全页可以使用driver.get_full_page_screenshot_as_png()Chrome支持或者用CDP命令截长图。不过全页截图比较吃内存一般只在测试失败时用就行。3.4 iframe与多窗口切换绕开最常见的“找不到元素”坑还有一个经常让新手抓狂的场景页面里嵌套了iframe你在父页面里找iframe里的元素怎么都找不到。道理很简单iframe本质上是另一个文档Selenium默认只认识当前文档的元素。要操作iframe里的内容必须先切进那个iframedriver.switch_to.frame(iframe-name-or-id)如果iframe没有name和id可以用index或WebElement切换iframe driver.find_element(By.XPATH, //iframe[src...]) driver.switch_to.frame(iframe)切进去之后所有元素定位都发生在iframe内部。操作完了要切回主文档driver.switch_to.default_content()多窗口切换也是类似逻辑。点击一个带有target_blank的链接会打开新标签页这时候Selenium的driver上下文还停留在原页面你需要切换到新窗口handles driver.window_handles driver.switch_to.window(handles[-1])一般建议先用driver.current_window_handle记录原窗口操作完新窗口后再切回去。窗口切换的坑在于新窗口的页面可能还没加载完切换后马上定位元素也可能失败所以同样需要配合显式等待。4. 常见问题与排查技巧实录4.1 元素定位失败的典型原因与解决做Web自动化测试99%的“找不到元素”都可以归到下面几类第一类元素确实不在DOM里。可能是页面还没加载完或者是前端根据用户权限动态渲染。解决办法就是用显式等待哪怕是最简单的visibility_of_element_located都能覆盖大多数情况。第二类元素在iframe里。前面说了先switch_to.frame再定位定位完记得切回来。第三类元素被遮挡点击的时候提示“element click intercepted”。这通常是弹窗、遮罩层、Cookie横幅盖在元素上面。你可以先关闭遮罩或者用JS直接点击driver.execute_script(arguments[0].click();, element)用JS点击可以绕过遮挡但你要清楚这可能让脚本脱离真实用户行为。如果页面确实需要点击才触发事件JS点击也是触发的大多数情况下没问题。不过用这个办法之前最好先想想遮罩为什么没关掉说不定是你的脚本破坏了页面状态。第四类元素属性是动态的。比如id里面带随机数每次刷新都变。这种元素天生不适合用ID定位建议改用稳定的class或相对位置。如果非要用动态ID就用XPath匹配前缀# 不推荐但确实有项目这么干 driver.find_element(By.XPATH, //input[starts-with(id, dynamicField_)])这里要敲个警钟不要一上来就复制工具的XPath。XPath定位越弱脚本未来维护成本越高。定位策略的选择直接决定自动化用例能活多久。4.2 WebDriver版本不匹配与驱动管理再强调一次驱动版本问题因为它实在太常见了。Chrome浏览器一旦自动更新原来的chromedriver就可能报错Message: session not created: This version of ChromeDriver only supports Chrome version 109解决办法有两个一个是手动去下载对应版本的chromedriver然后替换另一个是使用前面推荐的webdriver-manager让它自动处理。我强烈建议测试环境把Chrome的自动更新关掉或者在CI镜像里固定Chrome版本否则你永远不知道哪一天驱动就挂了。如果你的项目是边缘浏览器Edge、Firefox同理Edge也有msedgedriverFirefox是geckodriver都是同样的匹配规则。webdriver-manager支持这些浏览器甚至是远程Selenium Grid。4.3 被网站风控识别之后怎么办这个点是很多人在搜索引擎里喜欢搜的但我要先泼一盆冷水自动化测试和恶意爬虫是两码事。正常Web测试是在自己的测试环境或自己公司产品上运行如果在自己产品上做自动化测试都触发风控问题大概率出在测试环境不具备识别白名单的条件。如果你是被外部站点拦截那说明你的目标本身就不是为了测试那不应该用Selenium去做。从测试工程的角度来说如果测试环境有风控策略我们通常这么做把测试账号加入白名单这是最正规的方案。测试环境关闭验证码、滑块等风险校验。尽量模拟真实用户操作节奏比如在两步操作之间随机等待0.5到1.5秒而不是连续无间隔地点击。有人会尝试修改navigator.webdriver属性来绕过检测技术上确实可行但你要想清楚用途。如果是测试自己公司的系统我建议直接找开发把识别逻辑绕过掉或者加上白名单这才是工程的正解如果是测试第三方系统未经授权绕过风控会涉及法律风险一定不要做。我们在博文里提这一点是希望大家明确边界而不是教人怎么钻空子。如果你的自动化脚本确实因为“操作太快”触发了防抖逻辑最有效的优化就是增加随机延时。用time.sleep配合随机数import time, random time.sleep(random.uniform(0.5, 1.5))这段代码被很多人当成“反爬虫”神器其实它只是模拟人类操作节奏只能解决一部分基于频率检测的风控。如果测试系统还有更严格的行为模型分析那就不是Selenium单方面能解决的老老实实找开发开白名单更高效。4.4 执行速度慢与稳定性优化自动化测试跑起来慢最常见的原因是等待时间设置不合理。有人为了图省事在关键操作前都sleep(5)一个用例30个操作光等待就150秒完全没有意义。优化方向是把固定sleep改成显式等待让脚本在元素出现的第一时间就继续执行。另外还要检查浏览器驱动有没有使用无头模式options webdriver.ChromeOptions() options.add_argument(--headlessnew) driver webdriver.Chrome(optionsoptions, serviceservice)无头模式不会弹出浏览器窗口性能损耗会小一些特别适合在CI服务器上跑。但要注意无头模式下有些页面行为会和有头模式有差异比如视频播放、某些字体渲染所以无头模式建议只在快速冒烟测试或CI集成中使用真正的验收测试还是建议用有头模式。稳定性优化还有一招就是给浏览器启动设置合理参数。比如禁用GPU、禁用沙箱、设置窗口大小都可以避免一些莫名其妙的崩溃options.add_argument(--disable-gpu) options.add_argument(--no-sandbox) options.add_argument(--window-size1920,1080)--no-sandbox在Linux CI环境中几乎是必须的但这个参数有安全隐患仅限隔离的CI环境使用。本地开发不建议。4.5 常见异常速查表异常信息常见原因解决思路ModuleNotFoundError: No module named seleniumSelenium未安装或装到了其他Python环境用python -m pip install seleniumSessionNotCreatedException驱动版本和浏览器版本不匹配用webdriver-manager自动匹配或手动下载对应版本NoSuchElementException元素未加载、在iframe或定位符错误显式等待、切换iframe、检查定位表达式ElementClickInterceptedException元素被遮挡或处于不可用状态关闭遮罩等待可点击必要时JS点击TimeoutException显式等待超时确认等待条件是否合理页面是否有异常StaleElementReferenceException页面刷新导致元素重新渲染旧的引用失效重新获取元素对象不要复用长时间缓存InvalidArgumentException传入参数不合法比如send_keys传非字符串检查输入类型这张表我建议你保存下来等实际遇到问题再回来看比自己查英文文档快得多。5. 从一个测试脚本到测试框架的演进5.1 用pytest组织测试用例告别“脚本散落病”会写Selenium脚本只是第一步真正能落地的自动化测试必须把用例组织成可维护、可筛选、可报告的形式。这里我推荐pytest它比unittest更灵活插件生态也更活跃。最小化的pytest用例长这样# test_login.py import pytest from selenium import webdriver pytest.fixture def driver(): driver webdriver.Chrome() yield driver driver.quit() def test_login_success(driver): driver.get(http://localhost:8080/login) driver.find_element(By.ID, username).send_keys(admin) driver.find_element(By.ID, password).send_keys(123456) driver.find_element(By.ID, submit).click() assert driver.current_url http://localhost:8080/homepytest.fixture的yield写法保证了每个用例结束后driver.quit会被执行即使断言失败也会走清理逻辑不会泄漏浏览器进程。pytest还支持用例筛选比如只跑包含“登录”的用例pytest -k login --tbshort在大型项目里你可以把测试分成冒烟、回归、全量几组通过pytest.mark标记pytest.mark.smoke def test_basic_login(driver): ...运行的时候pytest -m smoke即可。这个机制让我们可以每天跑快速冒烟版本发布前跑全量回归。5.2 数据驱动测试一套用例测多组数据Web测试里经常遇到“同一段流程换不同输入验证不同结果”的场景。比如登录测试既要有正确密码登录成功的正例也要有错误密码提示失败的反例还要有空用户名、边界值等。如果每种情况写一个用例代码大量重复维护成本高。这时候就需要数据驱动。pytest的parametrize装饰器就是数据驱动最直接的工具pytest.mark.parametrize(username,password,expected, [ (admin, 123456, 登录成功), (admin, wrong, 密码错误), (, 123456, 用户名不能为空), ]) def test_login(username, password, expected, driver): driver.get(http://localhost:8080/login) driver.find_element(By.ID, username).send_keys(username) driver.find_element(By.ID, password).send_keys(password) driver.find_element(By.ID, submit).click() text driver.find_element(By.ID, result).text assert text expected这样一组数据就是一条用例报告你可以清楚看到哪组数据挂掉。如果数据特别多可以把它们放到独立文件里如test_data.yaml用pytest的hook把外部文件加载成参数这也是测试框架里常见的做法。数据驱动的核心思路是“测试代码与测试数据分离”。测试代码只关心“怎么执行操作”数据文件关心“测哪些输入”。以后产品需求变了多改数据少改代码维护成本自然降下来。5.3 从跑通到持续集成接入CI与生成报告单个测试脚本可以在本地跑但要真正发挥作用还得让它在代码提交后自动跑起来。接入持续集成CI的方案有很多最常用的GitHub Actions、Jenkins、GitLab CI都能直接跑pytest命令。以GitHub Actions为例一个简化流程- name: Install dependencies run: pip install -r requirements.txt - name: Run tests run: pytest --htmlreport.html重点是CI环境要提前装好浏览器和驱动。在Linux容器里webdriver-manager能自动下载驱动但Chrome浏览器本体需要额外安装。现在也有专门的Docker镜像比如selenium/standalone-chrome直接用它作为测试容器里面自带Selenium Server和浏览器就不用自己折腾环境了。报告的呈现上pytest-html插件生成基础HTML报告Allure报告是另一个更美观的选择。Allure会把每个用例的截图、日志、步骤都整理成结构化报告交互体验好很多团队评审测试结果时非常直观。安装pip install allure-pytest运行时加参数--alluredirallure-results再执行allure generate命令就能生成HTML站点。如果你刚开始做建议先用pytest-html简单直接等报告需求变多了再上Allure。5.4 脚本的后置与幂等性日常被忽略但重要的细节最后讲一个容易被新手忽略的点测试脚本的“后置清理”和“幂等性”。很多测试脚本跑了一遍能过跑第二遍就失败原因是第一次跑完留下的数据污染了第二次。举个例子登录测试每次创建一个用户创建逻辑没有处理“用户已存在”的情况。第二次跑的时候创建用户接口返回失败用例自然就挂了。解决办法是在测试开始前做数据清理或者在脚本里调用“如果存在则删除否则创建”的幂等接口。Selenium操作层面的清理是用Cookie或session重置用户状态。测试数据尽量独立比如每条用例使用随机后缀的用户名。对于数据库测试用例自己负责清理自己产生的数据。我自己写自动化测试时有一个习惯每个用例结束尽量让系统状态回到“初始状态”。实在回不去也要保证用例之间互不依赖。不要写“先运行用例A再运行用例B因为B依赖A登录产生的数据”这种脚本跑一次能过换顺序跑就崩排查起来极其痛苦。另外就是脚本的健壮性。Selenium操作过程中页面偶尔会出现网络慢、资源加载失败导致弹白板。这种偶发问题不应该直接判定为用例失败。我一般会在断言前加一个失败重试机制。pytest原生重试需要插件pytest-rerunfailurespip install pytest-rerunfailures使用方式pytest --reruns 3 --reruns-delay 2对于网络抖动等不稳定因素重跑3次能避免很多误报。但一定不要把这个当成“掩盖BUG”的手段。一个用例如果重跑3次还是失败那就不是抖动问题是真BUG了。真正合格的自动化测试长期稳定率应该在95%以上如果频繁依赖重试说明脚本本身待优化而不是先加重试。我个人在项目里的实际体会是Selenium自动化测试最大的难点从来不是“点击按钮”而是“让点击按钮这件小事在十种环境下都稳定复现”。Python和Selenium给了我们足够灵活的API但脚本能不能真正降低回归成本取决于你对页面细节的敏感度和对测试工程化的坚持。我会在每一个可能出错的环节都留一手——等待、截图、数据清理、报告这些看上去不起眼的功夫才是自动化测试能持续给你带来价值的关键。
延伸阅读

更多相关文章

2026/10/11 6:12:45

Agent沙箱是什么?从隔离原理到容器选型与实操指南

最近不止一个朋友跑来问我同一个问题:“你天天把沙箱挂在嘴边,张口闭口‘让Agent跑在沙箱里’,沙箱到底是个啥?”这个问题听起来特别基础,但真要三言两语讲清楚,还真不是一件容易事。说白了,沙箱…

2026/10/11 6:12:45

Java封装到底保护了什么?从private到业务校验的落地实践

大概两年前,我接手过一套订单系统的维护任务。系统本身不算复杂,线上却总出怪事:隔几天就会出现一笔金额为负的订单记录,个别订单的数量也被人改成个位数。排查到最后才发现,不是推送接口写错了,也不是数据…

2026/10/11 7:12:47

海思3519DV500相关命令

海思3519DV500相关命令1.文件系统烧录命令2.Uboot设置网络命令3.Uboot烧录命令1.文件系统烧录命令 dd if/run/uImage-fdt of/dev/mmcblk0p4 bs4Mdd if/run/rootfs_hi3519dv500_96M.ext4 of/dev/mmcblk0p5 bs4M2.Uboot设置网络命令 # 倍数为512倍 setenv serverip 192.168.1.18…

2026/10/11 7:12:47

AI产品经理掌握格式塔原理,产品真的会更懂用户

亲爱的小伙伴,如有帮助请订阅专栏!跟着老师每课一练,系统学习AI产品经理课程! 《AI产品经理入门实战》https://edu.csdn.net/course/detail/41126《Axure原型设计精品课》https://edu.csdn.net/course/detail/40420 前两天跟一个…

2026/10/11 7:12:47

国内车企数据闭环实践对比:蔚来群体智能 vs 小鹏众包采集

上一篇拆完特斯拉 Data Engine,粉丝留言最多的问题是:特斯拉靠先发百万车队建立了数据霸权,国内车企拿什么追?答案其实藏在同一句话里——用车队规模换模型进化速度。蔚来 NAD 和小鹏 XNGP 走的是同一条大路:不建庞大的…

2026/10/11 7:07:47

优秀产品经理与糟糕产品经理:产品 CEO 的自我修养

一、引言:产品经理就是产品的 CEO优秀的产品经理对市场、产品、产品线以及竞争对手都有深入理解,并把这些理解建立在实际知识和稳定判断之上。可以说,一个优秀的产品经理就是产品的首席执行官:他承担全部责任,以产品的…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑