拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Selenium自动化测试实战:从框架设计到滑块验证码与稳定交付

1. 项目概述与价值定位Selenium 自动化测试工具很多人第一反应是“不就是个录制回放工具吗”或者是“会写 Python 就能上手”。真正做过企业级客户项目的工程师都清楚初学者与熟练工之间的差距恰恰体现在那些文档里不会写、教程里没人提的细节上。这次我把自己作为乙方面向客户落地 Selenium 自动化测试实战项目的完整经历整理出来从方案设计、技术选型到交付维护覆盖一个真实项目从 0 到 1 的全过程适合刚入行想接触真实自动化项目的新人也适合正在准备把自动化测试从“能用”推向“可靠”的测试开发。这个项目本身并不高大上客户是一家做企业级管理系统的厂商系统基于 Vue 2 构建页面交互复杂包含大量异步加载、弹窗嵌套、权限控制逻辑以及一个多少有点麻烦的图形拼图验证码。客户最初的需求很简单“我们要把核心业务链路自动化起来减少回归测试人力。”但潜台词其实很务实——要能跑、能稳定跑、出了问题能快速定位到是环境问题还是代码问题。整个项目做了大概 6 周覆盖主流程用例 60 余条最终交付了一套基于 Python Selenium 的自动化测试工程配合 Pytest 管理用例、Allure 出报告、Docker 做环境隔离全套代码托管在客户的内网 GitLab 上。这套方案的选型和落地过程其实就是“从功能测试升级到自动化测试基建”的一个缩影。它解决的问题很典型团队人少、回归压力大、系统模块之间耦合严重、每次发版都靠手工点一遍。所以这篇博文里我不打算只贴代码而是把思路、坑、判断依据都讲透尤其是那些必须走到项目里才会理解的细节。你可以把它当成一份“客户实战项目踩坑笔记”也可以当一份“下一步就该照抄的方案模板”。2. 整体架构设计与技术选型思路2.1 为什么用 Python 而不是 Java选型是项目启动后的第一个分歧点。客户方原有的研发团队主力语言是 Java测试组的同事平时做接口测试用 JMeter 比较多对代码并不陌生。当时我们内部讨论了两个候选方向一是 Java Selenium WebDriver TestNG二是 Python Selenium Pytest。双方各有道理但最后我们选了 Python原因有三个。第一脚本编写效率高。Python 在 Web 自动化测试场景下的表达能力远超 Java尤其是处理动态定位、嵌套 iframe、弹窗切换这类反人类逻辑时Python 的代码量明显更少可读性也更好。对于客户团队里“会写一点脚本但不太深入”的测试同学来说Python 门槛更低后续交接维护的阻力更小。第二生态工具链成熟。Python 在数据断言、Excel 读取、YAML 配置解析、报告生成这些周边环节上的支持非常完善pytest 的插件体系也比 TestNG 更适合“既要管理用例又要输出漂亮报告”的场景。项目里我们用了 pytest-xdist 做多线程执行pytest-assume 做实失败不中断这些如果要在 Java 体系里配齐折腾成本会高很多。第三团队技术栈迁移成本低。客户团队虽然主语言是 Java但很多人用 Python 写过数据处理、爬虫脚本有一定基础。Selenium WebDriver 本身是语言无关的协议驱动核心逻辑在 Python 里跑和在 Java 里跑原理一致选 Python 本质上是在保证自动化测试核心能力不掉链子的前提下降低了团队的后续维护门槛。我没有全盘否定 Java 方案。如果客户系统的自动化脚本需要深度集成到他们已有的 Java 构建链路或者团队有强 Java 背景那选 Java 完全没问题。但在这个具体项目里Python 的取舍是合理的。2.2 自动化测试框架的四层结构项目落地时我直接给客户方案拆成了四层结构。很多 Selenium 新手写脚本最大的问题是把所有东西揉在一起一个用例文件里塞了元素定位、操作步骤、数据管理、断言逻辑一旦业务变了维护成本直接爆炸。这套四层结构是实际项目里验证过最稳的方案。层次职责具体实现用例层定义业务场景组合操作步骤pytest 用例文件一个用例对应一个客户核心流程页面对象层封装页面元素和操作方法Page Object 模式每个页面一个类工具层提供通用能力浏览器驱动管理、等待封装、截图、日志、报告配置层管理环境差异和数据YAML 配置文件区分 dev/test/prod 环境这个分层的核心思想就一句话业务操作与实现细节分开。用例层只关心“我要做什么业务”页面对象层负责“具体怎么做”工具层解决“怎么稳定地做”。比如客户系统里的登录操作用例层只需要调login_page.login(username, password)至于登录按钮怎么定位、等待几秒、失败怎么截图全在页面对象层里处理。我一直强调页面对象模式是 Selenium 项目的基石。以前带过的练习者总抱怨“定位不到元素”“脚本昨天还跑今天我调浏览器就挂了”十有八九是把定位逻辑直接写在用例里改一处要翻遍所有用例。页面对象层的价值不是减少多少代码量而是把变化的点收口到一个地方后续维护时只需要改一个文件。2.3 驱动管理与测试环境隔离Selenium 项目里最容易被忽略又最恶心的问题是浏览器驱动管理。很多初学 Selenium 的人第一步就摔在这——本地装了 Chrome写了脚本一跑报错session not created一看是 chromedriver 版本和浏览器版本不匹配。这个项目里我们直接把驱动管理自动化了用的工具是 webdriver-manager这是个 Python 库能够自动检测当前浏览器版本下载对应驱动省去手工维护。pip install webdriver-manager然后在工具层封装一个带默认参数的获取浏览器对象的方法统一管理浏览器实例的创建。实际项目里我封装了 Chrome 和 Firefox 两套配置默认走 Chrome因为客户测试环境还有一部分老系统依赖 IE 模式的 Edge 兼容但我们没有把老系统纳入本期范围所以 Chrome 足够。环境隔离这块我得专门提一句。客户有测试环境和预发布环境两套环境的域名不同、部分测试数据不同如果把这些硬编码到脚本里换环境时光改代码就能改崩溃。我们的做法是在配置层维护一套base.yaml再按环境拆分dev.yaml、staging.yaml运行时通过 pytest 的--env参数指定环境配置加载模块会根据参数拼接配置文件的路径。这样 CI 里跑测试和本地联调完全不用改代码。2.4 图形滑块验证码的前置处理方案这个项目最折腾的地方之一是登录环节有一个图形拼图验证码。客户系统上线之前接了一个第三方风控组件登录时经常弹出滑块拼图要求把拼图块拖到缺口位置。这个东西在人工测试时就是几秒钟的事但自动化脚本想要过它难度直接上一个台阶。当时团队内部讨论过几种方案第一种是用第三方打码平台花钱省事但客户明确不接受外部服务介入核心业务系统第二种是接入无头浏览器并处理 WebGL 指纹但这个验证码组件本身就是检测自动化的一旦识别到 CDP 协议或异常指纹直接锁账号第三种是图像识别 模拟拖动轨迹这是当时最可行的方案。最终我选的是图像识别方案主流程如下定位拼图验证码的背景图元素和滑块元素通过 Selenium 截取元素区域图片。用 OpenCV 处理背景图灰度化、边缘检测找到缺口位置。计算缺口位置与滑块初始位置之间的横向位移。构造模拟人的拖动轨迹先快后慢再加抖动用 ActionChains 拖动滑块。拖动完成后轮询验证码区域判断是否消失或出现成功状态。这个方案不是 100% 成功实际跑通率大概在 80% 左右。但从项目的角度能接受因为我们给它加了两层保险一是失败后自动刷新验证码重试最多重试 3 次二是整个自动化测试用例失败后会自动截图并通过企业微信机器人通知到维护人员人工介入兜底。我特别想强调一点接手自动化测试项目时如果发现核心链路卡在第三方验证码上第一反应不应该是“硬碰硬”而是评估验证码的出现频率、业务环节的重要程度、以及绕开它会不会影响测试覆盖。如果验证码只在凌晨或者异地登录时出现那完全可以通过测试账号的白名单机制避免同时降低安全风险。这个项目里因为风控组件是客户的核心诉求之一不能绕过所以才选择图像识别硬解。3. 核心实现细节与实战拆解3.1 页面对象模型的标准写法前面反复提到页面对象模式这里用一个真实的例子展示标准写法。以客户系统的登录页为例核心代码结构如下。# pages/login_page.py from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from utils.config_reader import ConfigReader from utils.screenshot import ScreenshotHelper from utils.logger import Logger class LoginPage: def __init__(self, driver): self.driver driver self.logger Logger.get_logger(__name__) self.config ConfigReader.get_env_config() self.url self.config[base_url] self.screenshot ScreenshotHelper(driver) self.username_input (By.CSS_SELECTOR, input[nameusername]) self.password_input (By.CSS_SELECTOR, input[namepassword]) self.login_button (By.CSS_SELECTOR, button[typesubmit]) self.error_tip (By.CSS_SELECTOR, div.error-message) self.slider_verify_block (By.CSS_SELECTOR, div.slider-verify-container) def open(self): self.driver.get(self.url) self.logger.info(f打开登录页: {self.url}) WebDriverWait(self.driver, 10).until( EC.presence_of_element_located(self.username_input) ) def input_username(self, username): element WebDriverWait(self.driver, 10).until( EC.visibility_of_element_located(self.username_input) ) element.clear() element.send_keys(username) def input_password(self, password): element WebDriverWait(self.driver, 10).until( EC.visibility_of_element_located(self.password_input) ) element.clear() element.send_keys(password) def click_login(self): button WebDriverWait(self.driver, 10).until( EC.element_to_be_clickable(self.login_button) ) button.click() # 点击后等待页面跳转或登录结果避免下一操作直接抢跑 def get_error_message(self): try: tip WebDriverWait(self.driver, 5).until( EC.visibility_of_element_located(self.error_tip) ) return tip.text except Exception: return None def slide_verify_if_present(self): 处理滑块拼图验证码出现则处理不出现则直接返回 try: block WebDriverWait(self.driver, 3).until( EC.presence_of_element_located(self.slider_verify_block) ) self.logger.info(检测到滑块验证码开始处理) # 此处调用滑块处理工具后文详细展开 return True except Exception: return False def login(self, username, password): self.open() self.input_username(username) self.input_password(password) self.slide_verify_if_present() self.click_login()这段代码有几个细节值得展开说明。为什么元素定位用元组而不是直接用find_element因为用元组存储定位条件后续无论用WebDriverWait、pytest的断言还是自定义封装都能直接复用。这看起来是小优化但项目里元素一多你就会发现统一管理定位符的价值。为什么要包裹这么多WebDriverWait因为客户这个系统大量使用 Vue 2 的异步渲染页面数据是接口返回后动态插入 DOM 的。如果加载完成后马上操作元素经常出现元素存在但点击无效的诡异问题。expected_conditions里我用得最多的是visibility_of_element_located和element_to_be_clickable前者判断元素可见后者判断元素可交互两者场景不同不能混用。在填写输入框的场景应该用visibility_of_element_located因为输入框只要可见就能输入在点击按钮的场景应该用element_to_be_clickable因为按钮可能因为 loading 状态或 disabled 属性暂时不可点。登录页这里还埋了一个伏笔登录成功后系统会跳转到首页但跳转方式可能是路由跳转或整页刷新不同方式下等待条件不同。实际项目里我在用例层加了一个统一断言等待首页关键元素出现确保登录动作真正完成。3.2 滑块拼图验证码的 OpenCV 识别逻辑滑块拼图是客户项目里难度最高的技术点我单独用一节完整讲解实现细节。首先要明确一个基础认知滑块拼图验证码的本质是“找缺口位置”然后是“模拟人类拖动”两个环节的成败率会直接影响整体稳定性。找缺口位置的实现我最初写了三个版本第一个版本的问题很典型就是只做灰度化和边缘检测但背景图有很多干扰线条缺口轮廓并不清晰导致识别不准。我当时调试输出的轮廓图几乎无法稳定框出缺口。第二个版本改成背景差分思路是先截取无滑块背景图和有滑块背景图通过两张图相减找出滑块移动后的差异区域。这个方法在背景简单时很准但客户系统验证码的背景图是随机风景图且每次加载背景图都会轻微变化差分结果不稳定。第三个版本也是最终上线的版本思路是“边缘检测 轮廓筛选 坐标聚类”具体分四步。第一步截取验证码背景图。我用 Selenium 定位验证码容器元素先把元素滚动到可视区域再调用元素截图方法确保截下来的图不包含页面其他区域的干扰。第二步使用 OpenCV 处理import cv2 import numpy as np def find_gap_offset(bg_image_path, debugFalse): img cv2.imread(bg_image_path) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯模糊降噪减少背景纹理干扰 blurred cv2.GaussianBlur(gray, (5, 5), 0) # Canny 边缘检测 edges cv2.Canny(blurred, 100, 200) # 形态学闭运算把断裂的边缘连成整体 kernel np.ones((10, 10), np.uint8) closed cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel) # 找出所有轮廓 contours, _ cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 按轮廓面积排序排除过小和过大的噪点 candidates [] for contour in contours: x, y, w, h cv2.boundingRect(contour) area w * h if 800 area 20000: candidates.append((x, y, w, h)) # 缺口通常是面积最大的候选区域直接返回其中心 x 坐标 if not candidates: return None candidates.sort(keylambda item: item[2] * item[3], reverseTrue) x, y, w, h candidates[0] gap_center_x x w / 2.0 if debug: cv2.rectangle(img, (x, y), (x w, y h), (0, 0, 255), 2) cv2.imwrite(debug_gap.png, img) return gap_center_x参数调整是这个环节的重头戏。GaussianBlur的核大小、Canny的阈值、形态学闭运算的核大小每一项都和背景图的纹理复杂度相关。我调试时反复对比过风景图、纯色图、带水印的图最终确定的参数组合(5,5)、100,200、(10,10)在客户实际环境里表现最稳定。如果换到其他验证码系统这些参数很可能要重新调优。所以这条经验必须给到不要照抄别人的 OpenCV 参数务必针对你实际面对的验证码图片做一轮参数调试。第三步计算滑块拖动距离。滑块初始位置在验证码区域左侧有一段固定的偏移缺口中心坐标减去滑块中心坐标就是水平拖动距离。这个距离需要根据 Selenium 元素的实际像素宽度和截图缩放比例做换算。如果截图分辨率和网页渲染分辨率不一致还要按比例缩放。第四步模拟人类拖动轨迹。这一步很关键直接拖到目标位置大概率会被判定异常因为真实人类拖动滑块时不可能完全匀速起始会慢、中间快、接近目标时会顿一下微调。我用ActionChains的click_and_hold、move_by_offset、pause、release组合了一个三段式轨迹from selenium.webdriver.common.action_chains import ActionChains import time import random def drag_slider(driver, slider_element, distance): action ActionChains(driver) action.click_and_hold(slider_element) # 第一段慢速启动模拟手指按下后的犹豫 action.move_by_offset(distance * 0.2, random.uniform(-2, 2)).pause(0.2) # 第二段快速拖向目标但保留少量偏移 action.move_by_offset(distance * 0.6, random.uniform(-3, 3)).pause(random.uniform(0.1, 0.3)) # 第三段微调逼近 action.move_by_offset(distance * 0.2, random.uniform(-1, 1)).pause(0.1) action.release() action.perform()轨迹细节上我没有做得过于复杂因为真实人类的拖动其实也没有那么“丝滑”恰恰是这种带一点抖动的轨迹更容易通过检测。另外拖动过程中pause的时间也要随机化固定间隔反而像机器行为。这个实现让我想起机器学习项目里的说法太规律的数据往往不正常规律本身就是一种破绽。还要强调一点滑块处理一定要捕获异常超时或者识别不到缺口都不能让用例直接挂掉。我在工具层做了一个重试逻辑识别失败后点击验证码刷新按钮重新截取背景图最多重试 3 次。3 次都不过就截图留证并抛出异常交给用例层的失败截图逻辑处理。3.3 动态数据的读取与管理客户系统里面有几个业务流程严重依赖测试数据比如新增订单、编辑客户信息、审批流程每跑一次用例都需要生成一批新的数据否则两次执行之间会产生数据冲突。这个问题的解法是“测试数据分离 随机数据生成”。数据分离走的是 YAML 配置文件每个用例模块对应一个数据文件里面写清楚哪些字段是固定值、哪些字段是动态生成的# data/order_data.yaml order_create: customer_name: 自动化客户_{timestamp} product_code: P10086 quantity: 2 payment_method: 在线支付 remark: 自动化测试生成的订单可忽略Python 端读取时做一个模板渲染把{timestamp}替换成实际时间戳import yaml from datetime import datetime class DataProvider: staticmethod def load_data(data_key): with open(data/order_data.yaml, encodingutf-8) as f: all_data yaml.safe_load(f) raw all_data[data_key] return DataProvider._render(raw) staticmethod def _render(data): if isinstance(data, dict): return {k: DataProvider._render(v) for k, v in data.items()} if isinstance(data, str) and {timestamp} in data: return data.replace({timestamp}, datetime.now().strftime(%Y%m%d%H%M%S)) return data这套方案另一个好处是测试数据发生变更时只改 YAML 文件不需要动 Python 代码。客户系统后续增加了几个必填字段测试同事改配置就搞定了整个过程我没参与他们自己维护得挺好。3.4 用例层的业务组织方式前面几节讲的都是底层能力这一节回到业务本身。一个自动化测试项目能不能被客户认可最终还是要看用例能不能覆盖真实业务链路。我接收客户需求后做的第一件事不是写代码而是梳理业务链路清单。以客户系统为例核心链路是登录 → 进入工作台 → 创建订单 → 订单审核 → 订单出库 → 订单完成。这中间每一步都会跳转不同页面涉及不同模块。用例层做得比较薄主要做三件事准备测试数据、按页面对象层的方法执行业务动作、断言结果状态。import pytest from pages.login_page import LoginPage from pages.workbench_page import WorkbenchPage from pages.order_page import OrderPage from utils.data_provider import DataProvider class TestOrderFlow: def test_creat_order_full_flow(self, driver, env_config): # 1. 准备测试数据 order_data DataProvider.load_data(order_create) # 2. 登录 login LoginPage(driver) login.login(env_config[username], env_config[password]) # 3. 进入工作台 - 订单模块 workbench WorkbenchPage(driver) workbench.enter_module(订单管理) # 4. 创建订单 order OrderPage(driver) order.click_create_order() order.fill_order_form(order_data) order.submit_order() # 5. 断言关键结果 assert order.get_order_status() 待审核, 下单后状态应为待审核 assert order.get_success_tip() is not None, 应弹出成功提示用例层还有一个细节不同用例的执行顺序。pytest 默认按文件内顺序执行但业务链路有前后依赖我用了pytest-order插件管理用例顺序同时在用例之间通过断言做好衔接。比如“订单审核”用例的前提是“创建订单”用例执行成功如果前者跑挂后面的用例直接跳过而不是继续浪费执行时间。客户那边对测试报告最满意的是出现失败时能一眼看出问题出在业务哪一步。这得益于我们在每个业务步骤里都加了一层“步骤日志”失败时 Allure 报告里除了堆栈还有操作到哪一步的记录。这个其实不复杂就是在各业务关键节点打日志但很多人一开始不重视等用例失败完全摸不着头脑时才后悔没早做。4. 常见问题与排查技巧实录4.1 元素定位不到到底是谁的锅Selenium 实战里最高频的问题就是NoSuchElementException。我在这次项目里几乎每天都能遇到可能的原因分四类元素还没渲染出来、元素在 iframe 里、元素在 Shadow DOM 里、元素定位表达式本身写错。排查时我习惯按顺序确认先用页面源码搜索确认元素是否存在。如果源码里都没有说明页面还没渲染完成等待条件不够。如果源码里有但 Selenium 找不到看是不是 iframe 嵌套。可以用driver.switch_to.frame()切进 iframe 再定位。客户系统里有一个报表模块整个模块都放在 iframe 里一开始没注意定位失败排查了半小时。如果元素在 Shadow DOM 内常规定位方式无效需要先拿到 Shadow Host 再进入 Shadow Root 查询。最后才怀疑定位表达式问题。CSS 选择器里包含特殊字符比如冒号、方括号时需要转义。iframe 的处理经验我要单独强调因为现在的管理系统用 iframe 的并不多但一旦用了坑特别大。切换 iframe 后一定要记得切回主文档否则后续操作全乱套driver.switch_to.default_content()4.2 等待时间设多长才合适等待策略几乎是 Selenium 稳定性的分水岭。项目里我见过一段脚本全部用了time.sleep(5)测试环境不卡的时候跑得飞快一卡就到处失败。也见过全用固定隐式等待定位不到元素时报错看不到具体原因。这套项目统一运行的等待策略是三层体系浏览器级别设置driver.set_page_load_timeout(30)页面加载超过 30 秒直接失败避免无限等待。默认显式等待WebDriverWait(driver, 10)所有关键元素交互前都先等待元素状态。特殊慢接口单独放宽等待时间比如有一个报表导出的接口生成文件最长要 40 秒我们对导出成功状态设置了 60 秒的等待。等待时间没有万能值要根据业务响应时间调整。项目初期我建议用偏向保守的 10 秒稳定后再逐步下调到 5 到 8 秒缩短整体执行时间。4.3 用例不稳定与环境还是脚本有关客户项目运行一段时间后稳定性问题开始暴露。同一套脚本今天全过明天挂三条后天挂一条。这类问题排查起来极其消耗精力我总结了三板斧。第一板斧看截图和日志。我在工具层实现了失败自动截图截图里能看到页面当时的状态是弹了异常提示、页面崩溃还是数据没加载出来一看便知。第二板斧看执行环境的资源占用。客户测试服务器用的是 Windows Server上面跑了 Jenkins、数据库、被测系统一套环境晚上做自动备份时资源占用高测试就会经常超时。后来我们把自动化执行切到专门的测试机上和被测系统分离稳定性立刻提升了一个量级。第三板斧复现用户态。有些用例只在特定数据状态下失败比如“订单详情”页面某条订单被其他人提前审批了。这类问题不是脚本 bug而是测试数据冲突。解决方案是数据隔离每条用例使用独立的数据标识比如在客户名称后面加随机后缀。4.4 浏览器驱动版本不匹配的经典坑这个坑出现频率最高也最简单但每次都能坑到人。Chrome 浏览器每隔几周自动更新一次chromedriver 版本跟不上就报错。用 webdriver-manager 后这个坑基本被自动绕开但 CI 环境里如果自动检测不到浏览器版本还要手动指定版本。from webdriver_manager.chrome import ChromeDriverManager from selenium import webdriver options webdriver.ChromeOptions() options.add_argument(--headless) options.add_argument(--no-sandbox) options.add_argument(--disable-dev-shm-usage) driver webdriver.Chrome( ChromeDriverManager().install(), optionsoptions )注意无头模式下--no-sandbox和--disable-dev-shm-usage这两个参数在 Linux 服务器上几乎必须加否则 Chrome 启动会报错。我第一次在客户服务器上部署时被这个坑卡了两个小时发群里问才发现是共享内存不足的问题。4.5 一张问题排查速查表我把项目运行中遇到的高频问题整理成一张速查表方便后来者快速定位这在交付文档里也是客户使用频率最高的一页。现象可能原因排查步骤解决方向NoSuchElementException元素未渲染 / iframe / Shadow DOM页面源码搜索、检查 iframe 层级调整等待、切换 frameElementClickInterceptedException元素被遮罩或 loading 层挡住截图看当前页面状态等待遮罩消失或改用 JS 点击SessionNotCreatedException浏览器驱动版本不匹配对比 Chrome 和 chromedriver 版本用 webdriver-manager 自动管理用例偶发失败环境资源波动 / 测试数据冲突看截图和日志、查数据状态环境隔离、数据独立滑块验证码偶发失败背景图干扰 / 识别参数不匹配打开 debug 输出轮廓图调 OpenCV 参数、追加重试页面跳转后操作无响应Vue 异步渲染未完成检查 DOM 状态等待目标元素可见 可点击5. 成果交付与客户验收要点项目最终成果是一套完整的自动化测试工程包含页面对象层、工具层、用例层、配置层外加一份面向测试同学的实操文档。客户验收时的关注点非常集中我梳理一下他们真正看重的是什么。第一是稳定性报告。客户要求连续三天全量回归每天跑完统计通过率和失败原因。我们交付时连续三天的通过率都在 95% 以上剩余失败集中在滑块验证码识别和个别测试数据冲突且失败都有明确的截图和日志支撑。第二是报告的可读性。我把 Allure 报告的展示方式做了定制每个用例都有步骤描述、测试数据、截图、日志。客户方不懂代码的测试同事也能通过报告判断失败原因这一点比报告本身长得如何更打动客户。第三是可维护性。客户最担心的是项目交付后没人能维护。我在文档里专门写了一份“新手指南”从安装依赖、配置环境变量、运行指定用例、看报告到新增业务用例的完整示例。并在最终一周做了两次集体培训第一遍讲设计思路第二遍现场演示从新增需求到落地用例的完整过程。第四是环境一致性。测试执行环境从本机换到服务器后结果不能有显著差异。我把常见差异点都收敛到配置层保证环境切换零代码改动。客户验收后这套框架至今还在他们内部使用。后续他们把接口自动化测试也纳了进来与 Selenium 做 Web UI 自动化互补。中间有人提出过要不要换 Playwright客户也来问我意见。我说从技术演进角度 Playwright 确实有优势特别是自动等待和跨浏览器支持更省心但框架迁移意味着页面对象层、数据驱动、报告体系全部重写如果现有 Selenium 体系已经稳定运行、团队维护能力成熟迁移的性价比并不高。判断工具好坏要看团队和环境而不是只看框架的先进程度。我个人在多次实战后的体会是Selenium 项目能不能成功很多时候不是技术难题而是工程化程度问题。你有没有把测试数据管理好有没有统一处理等待和异常有没有让用例失败时能快速定位有没有给客户讲清楚脚本能做什么、不能做什么这些比会写几个 Selenium API 重要得多。最后再分享一个给自己的小建议也是这次项目里反复意识到的事情自动化测试脚本不是一次性交付物而是长期维护的“产品”。所有参与维护的人包括客户团队的测试和开发都应该能用同一套规则理解它、修改它、信任它。要做到这一点文档、规范、可读性和代码本身一样重要。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门