资讯动态

Pytest+Selenium实现Web登录自动化测试:从用例设计到稳定运行的完整实践

发布时间:2026/10/18 8:09:53 来源:尧图企业网站定制
最近接了个活儿用 Pytest Selenium 给一款 AI 助手 Web 端的登录功能做自动化测试也就是大家常说的 DeepSeek 登录模块那一类场景。这篇文章不打算写“hello world”式的入门教程而是把这套登录自动化里从需求拆解、用例设计、代码实现到问题排查的完整过程摊开来说。适合谁看如果你是刚开始做 Web 自动化、或者手动测试做腻了想往自动化方向靠又或者已经会写一点脚本但总被登录这种动态页面坑得怀疑人生那下面的内容应该对你有用。登录功能看起来简单做起来要多想几层不写自动化你根本意识不到它有那么多边角料问题。1. 项目背景与测试目标1.1 登录模块为什么值得单独做自动化很多团队做自动化测试喜欢先从核心业务链路下手比如搜索、下单、支付这些。但我的经验恰恰相反登录应该是第一个被自动化的模块。原因很简单登录是所有用户行为的前置条件一旦它出问题后面什么业务都跑不起来。而且登录模块的变化频率并不低前端改个样式、后端调个验证逻辑、加个第三方登录入口都可能把原有流程打乱。更重要的是登录功能表面上只有一个输入框加一个按钮实际上它的分支逻辑非常多账号不存在、密码错误、账号被锁定、验证码过期、手机号格式不对、第三方授权失败、会话超时。这些分支用手工测试去回归一轮下来至少需要二十分钟而且容易漏。自动化脚本可以把这些边界场景在几分钟内全部跑完还能在每次改版之后立刻回归一遍。我在做这套用例之前先统计了一下手工回归登录模块的耗时。常规操作是打开网页、输入测试账号、等验证码、点登录、再退出五到六个来回大概需要十五到二十分钟。如果要把异常场景也覆盖上耗时会翻倍。自动化之后同样的覆盖范围压缩到三到五分钟关键是它可以半夜跑早上上班直接看结果。1.2 为什么选 Pytest Selenium 而不是别的组合选型这个问题我几乎没有纠结。Selenium 是目前 Web UI 自动化最成熟的方案它支持主流浏览器生态完善遇到问题随便一搜就能找到解决方案这对测试脚本来说非常重要。相比 Playwright 和 CypressSelenium 的语法虽然显得啰嗦一些但它的稳定性和团队上手成本是实打实的优势。很多测试团队里并不是人人都会写代码Selenium 的老牌地位意味着能找到大量现成的参考实现学习曲线更平缓。Pytest 这边就更好选了。相比 Python 自带的 unittestPytest 的断言更简洁fixture 机制处理测试前置和后置条件非常顺手参数化功能天然适配登录这种多组数据验证的场景。而且 Pytest 的插件生态非常丰富pytest-html、allure-pytest、pytest-xdist 这些都能直接满足报告、并发、重试的需求。有人可能会问为什么不直接用 Robot FrameworkRobot Framework 的关键字驱动风格适合业务同事一起参与但到了复杂逻辑处理的时候还是要写 Python。既然最终都要写代码那不如直接用 Pytest灵活度更高。也有人问为什么不用 Requests 直接调接口测登录那样确实更快更稳但接口测试替代不了 UI 层面的验证比如按钮是否可点击、错误提示是否在正确位置展示、登录成功后页面元素是否正常渲染。最合理的做法是 UI 和接口测试分层但在项目早期先把 UI 自动化跑起来能最快暴露前端的问题。1.3 测试范围划定与用例设计策略登录模块测试范围的划定我建议遵循两个原则第一UI 自动化只覆盖关键路径和风险高的分支不要试图覆盖所有异常输入组合第二数据和用例分离把输入输出放在参数化数据里后续加用例不用改动脚本逻辑。我在这个项目里把登录用例分为四大类正常登录场景正确的用户名和密码验证码正确登录成功后跳转到首页并展示用户昵称。异常输入场景空用户名、空密码、错误密码、用户名不存在、密码长度超过边界、包含特殊字符等。环境限制场景网络超时、登录接口返回异常、页面刷新后 token 失效等。交互体验场景登录按钮在未输入内容时是否置灰、输入错误后是否有明确提示、点击登录是否重复提交。每种场景下再细分具体用例。这里有一个很容易犯的错误就是用例数量贪多。UI 自动化的用例并不是越多越好因为每一个用例都依赖浏览器执行跑起来慢而且元素变化容易挂。我一般控制登录模块的 UI 用例在二十条以内跑完控制在五分钟左右超过这个规模就说明用例粒度太细需要考虑拆到接口层去覆盖。2. 环境准备与工程架构2.1 本地环境搭建与版本匹配这套测试项目用到的环境依赖非常标准Python 3.10 以上Selenium 4.xPytest 7.x。第一步先把虚拟环境建起来避免污染系统 Pythonpython -m venv venv source venv/bin/activate pip install pytest selenium webdriver-manager pytest-html allure-pytest这里重点说一下浏览器驱动的问题。Selenium 本身只是一个驱动库它需要配合浏览器对应的驱动程序才能控制浏览器。最常见的问题是 chromedriver 的版本必须和 Chrome 浏览器版本匹配否则启动浏览器时直接报 session 创建失败。以前处理这个问题的办法是去官网手动下载对应版本的驱动放到 PATH 里。现在推荐用 webdriver-manager它会在运行时自动检测本地 Chrome 版本下载匹配的驱动省掉很多手工维护工作。如果你在的公司网络受限自动下载可能失败那就需要提前把驱动下载好放到指定目录然后通过 Service 指定路径加载。我在工程里还顺手加了一个告诉团队成员的依赖说明文件把 Python 版本、Chrome 版本、依赖库版本都写清楚。这个动作看起来不起眼但它真的能帮同事省下不少排查时间。测试环境的一致性是自动化脚本能稳定运行的隐形前提。2.2 测试工程目录结构与分工测试代码的组织方式我直接采用经典的 Page Object Model 模式这是目前 Web UI 自动化最主流的设计思路。核心思想是把页面元素和操作逻辑封装成独立的页面类测试用例只关心业务操作不关心元素定位细节。这样页面一旦改版只需要修改对应的页面类测试用例主体不受影响。工程目录结构大致是这样的login_test/ ├── conftest.py # pytest 全局 fixture ├── requirements.txt # 依赖清单 ├── pages/ │ ├── __init__.py │ ├── base_page.py # 页面基类封装通用操作 │ └── login_page.py # 登录页对象封装元素定位和操作 ├── test_cases/ │ ├── __init__.py │ └── test_login.py # 登录测试用例 ├── data/ │ └── login_data.py # 测试数据可用 Excel 或 YAML 替代 ├── utils/ │ ├── __init__.py │ └── screenshot.py # 失败截图工具 └── reports/ # 测试报告输出目录这个结构的核心是分层清晰。pages 层管页面操作test_cases 层管用例组织和数据调用conftest.py 管共享的测试前置条件。对于登录模块这种页面数量少、用例相对集中的场景这个结构完全够用。如果后续真的要扩展到整个产品线还可以在这个基础上加业务层、数据工厂层但现阶段没必要为了架构而架构。2.3 conftest.py 的核心配置与 fixture 设计conftest.py 是整个测试工程的底座我在这里定义了三个关键 fixture浏览器实例、登录页对象、测试数据。浏览器实例的 fixture 采用 function 作用域保证每个用例都拿到一个全新的浏览器会话用例之间互不影响。import pytest from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.service import Service pytest.fixture(scopefunction) def driver(): service Service(ChromeDriverManager().install()) options webdriver.ChromeOptions() options.add_argument(--start-maximized) options.add_argument(--ignore-certificate-errors) driver webdriver.Chrome(serviceservice, optionsoptions) yield driver driver.quit()这里有一个细节容易被忽略为什么要把浏览器实例的 scope 设为 function 而不是 class 或 module因为登录模块的用例彼此独立如果用同一个浏览器实例连续跑多个用例前一个用例留下的 cookie 和登录态会干扰后一个用例的初始状态。比如某个用例登录失败了页面停留在登录页下一个用例打开的时候可能还带着上一个用例的输入痕迹。用 function 作用域虽然慢一点但是稳。稳定性和性能之间测试脚本永远优先选稳定性。3. 登录用例实现与关键技术点3.1 页面对象封装与元素定位策略页面对象封装的核心是把元素定位和页面操作内聚到同一个类里。以登录页为例通常会有用户名输入框、密码输入框、登录按钮、错误提示区域、首页的用户头像或昵称。把这些元素全部定义为类属性再用方法表示用户行为。from selenium.webdriver.common.by import By from pages.base_page import BasePage class LoginPage(BasePage): username_input (By.CSS_SELECTOR, input[nameusername]) password_input (By.CSS_SELECTOR, input[namepassword]) login_button (By.CSS_SELECTOR, button[typesubmit]) error_toast (By.CSS_SELECTOR, .ant-message-error, .toast-error) def login(self, username, password): self.input_text(self.username_input, username) self.input_text(self.password_input, password) self.click(self.login_button) def get_error_message(self): element self.wait_presence(self.error_toast) return element.text元素定位策略上我强烈建议优先用 CSS 选择器而不是 XPath。CSS 选择器在 Selenium 中的执行效率更高语法也更简洁。只有在需要根据元素的文本内容定位、或者需要从兄弟节点、父节点反推目标元素时才考虑用 XPath。另外要注意避免使用动态生成的 id 或 class 属性。很多前端框架会在每次渲染时生成随机后缀比如 idlogin-btn_12345这种属性今天能用明天就废了。我在项目里会推动前端团队在关键测试节点上加>from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class BasePage: def __init__(self, driver): self.driver driver self.wait WebDriverWait(driver, 10) def wait_clickable(self, locator): return self.wait.until(EC.element_to_be_clickable(locator)) def wait_visible(self, locator): return self.wait.until(EC.visibility_of_element_located(locator))在登录场景里有一个等待的坑特别典型点击登录按钮之后页面可能先跳转再异步加载首页数据。如果脚本在点击之后立刻去断言首页元素大概率会扑空。正确做法是等待首页的标志性元素出现比如用户昵称、导航栏或者某个特有的页面标题而不是去 sleep 几秒。这背后体现的是一个重要的测试思维永远不要假设时间只验证条件。时间的快慢是环境决定的条件满足与否才是业务逻辑决定的。3.3 基于参数化的数据驱动用例设计登录模块的典型特点是同一个操作流程不同的输入数据期望不同的结果。这种场景和 Pytest 的参数化机制天然契合。用 pytest.mark.parametrize 装饰器把测试数据和预期结果组织成列表每跑一组数据相当于执行一个独立的用例。这样既省代码量又多跑出了覆盖度。import pytest from pages.login_page import LoginPage pytest.mark.parametrize(username,password,expected, [ (test_user_001, Passw0rd!, login_success), (, Passw0rd!, empty_username), (test_user_001, , empty_password), (test_user_001, wrong_password, password_error), (not_exist_user, Passw0rd!, user_not_exist), ]) def test_login_cases(driver, username, password, expected): page LoginPage(driver) page.open_login_page() page.login(username, password) if expected login_success: assert page.is_login_success(), 登录成功后应跳转到首页 else: actual page.get_error_message() assert actual, 应该出现错误提示信息这一段代码里测试数据直接写在装饰器里适合用例数量少的情况。后期如果数据量太多可以把数据抽取到 data/login_data.py 或者 YAML 文件里通过 fixture 加载。注意数据文件里的测试账号要和测试环境保持一致尽量用测试库专用账号不要碰生产账号也不要用真实用户数据。3.4 登录成功后的状态管理与用例隔离登录用例之间最大的干扰因素就是登录态。一个用例通过登录之后浏览器会保存 cookie这些 cookie 会影响后续用例的初始状态。为了避免这个问题我做了两层处理第一层每个用例都从完全干净的状态开始driver fixture 是 function 作用域浏览器实例每次新建cookie 天然隔离。第二层登录成功后的断言只验证页面关键元素不做多余操作。有些用例如果确实需要登录状态比如验证登录后才能访问的页面我不会把它和登录测试混在一起写而是单独放到需要权限的模块里。这里还要注意一个细节清理浏览器上下文。即便每个用例都新建 driver在某些情况下浏览器进程可能没有完全退出导致资源占用过高。在 conftest.py 的 teardown 阶段除了 driver.quit()我还加了 driver.delete_all_cookies() 和 driver.clear_local_storage() 的尝试性清理避免极少数场景下 cookie 没有及时失效。4. 执行过程与问题排查实录4.1 首次运行踩过的典型坑这套脚本从写完到稳定运行中间踩了不少坑。第一个坑是浏览器驱动版本不匹配。当时本地 Chrome 自动更新到了新版本但环境变量里还是旧版 chromedriver启动浏览器直接报 “session not created: This version of ChromeDriver only supports Chrome version xxx”。后来用 webdriver-manager 自动获取匹配版本这个坑就彻底解决了。第二个坑是元素定位超时。登录按钮的 class 属性里包含动态生成的字符串看起来差不多实际每次刷新都不一样导致定位失败。排查了半天才发现问题不在代码逻辑而是选择器本身不稳定。后来把定位改成相对稳定的层级结构比如通过 form 内 button[typesubmit] 这样向上找所在的表单容器问题立刻解决。第三个坑是 iframe 和窗口切换。登录模块为了支持第三方扫码登录页面里嵌了一个 iframe。第一次跑的时候 driver.find_element 怎么也找不到 iframe 里的元素报错说元素不在当前上下文里。后来查看页面结构才发现目标元素在 iframe 内部必须先 switch_to.frame 再操作里面的元素。这也是很多 Web 自动化新手会忽视的点。4.2 验证码和滑块验证的处理思路登录自动化里最让人头疼的大概率是验证码和滑块验证。图形验证码无法直接识别滑块验证又涉及拖拽轨迹模拟这些都天然反自动化。我的处理思路不是去破解它们而是从测试工程层面绕过去。实际操作中我会推动测试环境屏蔽验证码。具体做法有几种测试环境配置一个开关关闭验证码服务或者预留一个万能验证码只对测试环境生效再或者调用后端接口获取验证码的内容填到前端。最彻底的做法是登录接口层面直接打个令牌然后把这个令牌注入到浏览器 cookie 中绕开 UI 登录流程。这个方案适合需要登录态但本身不测登录逻辑的用例。如果你确实要测带验证码的登录流程那就必须在用例里预留人工介入步骤配合 pytest 的暂停机制让测试人员在验证码输入之后再继续跑。这种脚本虽然不能完全自动化但它保证了主流程能跑、验证码环节可控也算是一个折中方案。4.3 常见问题速查表我把跑登录自动化过程中遇到的高频问题整理成了表格后面团队接手的时候可以照着排查问题现象可能原因解决方案浏览器启动失败报 session not createdchromedriver 与 Chrome 版本不匹配使用 webdriver-manager 自动匹配或手动下载对应版本元素定位超时使用了动态属性或元素在 iframe 内更换稳定选择器检查是否需要 switch_to.frame元素报 not interactable元素被遮罩层遮挡或处于禁用状态先等待元素可点击必要时 scrollIntoView点击登录按钮后结果不稳定点击太快页面 JS 尚未绑定事件用 element_to_be_clickable 等待或先点击其他区域触发绑定用例之间登录态串了driver 作用域过大cookie 未清理fixture 使用 function 作用域执行前清理 cookie登录成功断言偶尔失败首页元素加载较慢等待首页标志性元素出现不要用固定 sleep扫码登录无法自动处理依赖第三方手机 App测试环境关闭扫码或采用 token 注入方案这个表格看起来简单但每一条都是实际测试踩坑换来的。建议你在自己的项目里遇到新问题也顺手填进类似的表格形成团队自己的问题库。4.4 失败截图与报告集成自动化测试的脚本挂掉不可怕可怕的是挂掉之后没有任何现场信息。我强烈建议给每个用例加上失败截图机制。实现方式很简单在 pytest 的 fixture teardown 阶段判断用例是否执行成功如果失败则调用 driver.save_screenshot。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: screenshot_path freports/failures/{item.name}_{int(time.time())}.png driver.save_screenshot(screenshot_path)报告方面我用了两个方向简单场景直接上 pytest-html执行完之后会生成一个带测试用例明细的 HTML 报告如果团队有集中展示需求就配置 allure-pytestAllure 的报告在展示维度上明显更丰富可以按功能模块统计、查看每个用例的步骤和截图。pytest -v -s --htmlreports/login_report.html --alluredirreports/allure-results allure generate reports/allure-results -o reports/allure-report --clean跑完去打开报告看一眼就能看出哪些用例挂了、为什么挂、失败瞬间的页面长什么样。这比看控制台日志高效太多。4.5 并发执行与稳定性处理登录用例数量不算多但为了演示扩展开可以提一下并发。pytest-xdist 插件可以让多个用例并行执行命令加上 -n 参数即可指定并发数。但并发执行有一个前提用例之间必须绝对独立不能共享任何状态。登录用例天然满足这个条件因为每个用例都是新建浏览器、独立 cookie所以配上 -n 4 能显著缩短整体执行时间。不过我并不建议把所有 Web UI 用例都丢到并发里跑。浏览器实例本身很吃资源并发数开太高容易把测试机卡死。实际项目里我一般先串行跑两遍确认脚本稳定再考虑并发优化。稳定第一速度第二。5. 经验总结与后续扩展5.1 登录自动化的三层执行经验写这套登录自动化给我最大的体会是三层递进的关系第一层是页面能打开、元素能定位、步骤能跑通这属于“能跑”第二层是数据分离、用例独立、异常能自动截图这属于“稳定”第三层是能和 CI 流程集成、报告自动发送、失败自动定位这属于“好用”。大多数初学者的脚本停在第一层跑一次行跑十次就开始挂。而真正的自动化测试项目追求的是第十次和第一次结果一致。要跨过第一层到第二层核心是做好等待策略和用例隔离。要跨过第二层到第三层核心是做好结果反馈和报告沉淀。每一层都有不同的技术难点但都是可以通过持续迭代逐步完善。5.2 从 UI 测试走向接口与 UI 分层登录自动化稳定跑了一段时间之后我开始琢磨怎么让这套东西的效率更高。核心思路是分层接口测试覆盖绝大多数异常分支UI 测试只保留关键路径和交互验证部分。接口测试跑得快、维护成本低UI 测试保留少量冒烟用例确保真实用户路径可用。具体操作上我会用 Requests 或者 Pytest 的 fixtures 直接请求登录接口验证返回的 token、错误码和用户信息。这些用例不依赖浏览器跑起来秒级完成可以覆盖密码错误、用户不存在、参数异常这些高频分支。而 UI 层只保留四条核心用例正常登录、空用户名、空密码、错误密码提示。两层加起来既保证了覆盖面又把执行时间控制在了合理区间。5.3 与 CI 流水线的集成方向如果你的团队有持续集成环境登录自动化完全可以做成流水线里的一个质量门禁。思路是将 pytest 执行命令、报告生成命令、甚至失败重跑机制都写进流水线脚本每次代码合并前自动跑一遍登录冒烟用例。我在实际项目里用过两种方式一种是 Jenkins 定时任务每天凌晨跑全量用例上班看报告另一种是提交触发只在登录模块相关文件变更时触发执行。无论哪种方式核心是让测试结果的反馈链路足够短。用例挂在流水线里责任人第一时间收到通知点开报告就能看到失败截图和堆栈信息。这样自动化测试的价值才真正释放出来而不是变成一个“每周跑一次但没人看”的形式主义。按我的实际经验登录自动化跑起来之后真正省时间的地方不在于你不用手动点那几下而在于它逼你把登录模块的所有边界条件都梳理了一遍。我在写这套用例时最深的体会就是元素定位和等待策略的可靠程度决定了这套脚本到底能用一周还是能用一年。如果你也在写登录自动化建议先别贪用例数量优先把环境隔离和失败截图做扎实后面加用例会顺手很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑