只要跟Web开发沾边,就躲不开那种手指按到发酸的回归测试。上线前一天,对着浏览器一遍一遍走流程:填表单、点按钮、翻页、核对结果,出一个问题就得从头再来。用Python实现自动化的Web测试(Selenium),就是把这一整套重复劳动交给脚本去干。它能打开真实浏览器、模拟用户点击、输入、滚动、断言页面内容,还能在没人盯着的凌晨跑完整轮回归,早上把失败用例带着截图摆到你面前。Selenium是这个领域里最老牌也最普及的自动化测试框架,Python是它最好的搭档之一。这篇东西适合刚接触自动化测试的新手,也适合已经写了几个脚本、正被诡异报错折磨的同学。我会把环境搭建、定位逻辑、等待机制、工程化落地,以及自动化脚本被反爬机制误识别时的排查思路,完整过一遍。
1. 为什么自动化测试首选Python和Selenium
1.1 自动化测试到底解决了谁的痛点
先说人话:自动化测试解决的不是“写代码的乐趣”,而是“重复劳动带来的不可靠”。一个100步的流程,人点10遍,第7遍可能在某个下拉框上点错了,最后得出完全错误的结论。脚本不会点错,但脚本会老老实实把错误路径也走完、把结果记录下来,这才是它真正的价值。
拿我自己举例,之前维护一个老后台,每次发版前要验证登录、权限、录单、审核、导出五个模块,手工跑一遍大概四十分钟,出错重跑至少一小时。后来我把它们写成了30多个自动化用例,跑一遍大约六分钟。时间省下来不是最关键的,最关键的是:每次跑的结果都一样,不会因为今天状态差漏点某个按钮。自动化测试的定位从来不是替代手工测试,而是把手工里最高频、最回归、最容易倦怠的部分接过去。
1.2 Python和Selenium为什么是黄金搭档
市面上的Web自动化方案不少,Java、JavaScript、Ruby都能驱动Selenium,但我个人仍然推荐用Python。原因有三点:第一,Python语法简洁,写出来的用例几乎能当测试文档读,团队成员上手成本低;第二,测试数据、报表、CI脚本都能用同一套语言串联,不用来回切换技术栈;第三,Python社区里关于Selenium的踩坑记录最多,遇到问题搜索一下基本都有答案。
Selenium本身也不只是一套API。它支持Chrome、Edge、Firefox、Safari等主流浏览器,而且Selenium 4开始全面走W3C WebDriver标准,这意味着你写的脚本换浏览器跑时,绝大多数代码可以原样复用。对各浏览器兼容性要求高的团队来说,这是很现实的需求。它还有一个隐藏价值:由于脚本可以驱动真实浏览器,很多“网页数据抓取”的场景其实也是同一套技术栈,爬虫工程师和自动化测试工程师经常互相参考代码。
1.3 一场自动化测试在底层发生了什么
理解Selenium的运行机制,对你排查问题帮助很大。简单类比:Selenium脚本就像一个“遥控器”,浏览器则是被遥控的“电视”。脚本通过WebDriver协议向浏览器发送指令——打开网址、查找元素、点击、输入文字、读取页面标题。浏览器执行完后,把结果再传回给脚本。你写的Python代码,本质上是一连串发出去的命令和针对返回结果的断言。
这个机制决定了几个排查思路:如果脚本报“找不到元素”,很多情况下不是脚本写错了,而是命令发出时浏览器里的页面还没渲染完;如果浏览器根本没启动,问题大概率出在驱动和浏览器版本不匹配上;如果页面能打开但点不动,可能是元素被弹窗或遮罩层挡住了。万事归结到一句话:你是在操作一个真实浏览器,所以必须尊重页面的加载节奏和DOM状态。
2. 环境准备:Python、Selenium与浏览器驱动的坑
2.1 把环境从零装起来
配置环境这块最容易劝退新人,实际上只要按顺序一步步来,十分钟能搞定。
第一步,装Python。去Python官网下载安装包时,记得勾选“Add Python to PATH”选项。很多人装完在终端敲python没反应,基本都是这一步漏了。装完可以用括号里的命令验证:python --version。VSCode用户还需要装Python扩展,然后在命令面板里选择解释器,建议直接选当前Python版本,不然编辑器右下角会一直提示“未选择解释器”。
python --version pip --version第二步,安装Selenium:
pip install selenium第三步,准备浏览器驱动。Chrome浏览器就用ChromeDriver,Edge浏览器就用Edge WebDriver,Firefox就用geckodriver。驱动版本必须和浏览器版本匹配,这是头号坑。如果你想省事,Selenium 4.6以上内置了Selenium Manager,它会尝试自动下载匹配的驱动。但受网络环境影响,很多时候自动下载不顺畅,我建议新手直接用最稳妥的方式:打开浏览器的“关于”页面看版本号,去对应驱动官网下载相同大版本的驱动文件,解压后丢到任意目录。
2.2 驱动路径与启动问题的处理逻辑
有些教程会让你把驱动放到Python目录里,或者配置环境变量。我现在的做法更直白:用Service对象显式指定驱动路径,这样一眼就能看出脚本在用哪个驱动,出问题也好排查。
from selenium import webdriver from selenium.webdriver.chrome.service import Service service = Service(executable_path="D:/tools/chromedriver/chromedriver.exe") driver = webdriver.Chrome(service=service) driver.get("https://example.com") print(driver.title) driver.quit()如果启动时报SessionNotCreatedException,九成是这个提示的下一行里写了“This version of ChromeDriver only supports Chrome version xxx”,翻译过来就是驱动和浏览器版本对不上。解决办法不是去改代码,而是重新下载匹配版本的驱动文件。这一点值得反复强调:自动化测试里很多玄学问题,最后都是环境问题,不是代码问题。
2.3 第一个有序、能跑的脚本
装好环境后,第一段脚本不要写复杂,就做一件事:打开一个网页,打印标题,关闭浏览器。这段代码跑通了,说明环境没有问题,后面学元素定位才有意义。
from selenium import webdriver options = webdriver.ChromeOptions() options.add_argument("--window-size=1920,1080") # 固定窗口大小,避免页面响应式布局坑 driver = webdriver.Chrome(options=options) driver.get("https://example.com") print("页面标题:", driver.title) driver.quit()跑完之后说两点心得。第一,driver.quit()和driver.close()不一样,close()只关当前标签页,quit()会结束整个浏览器进程。日常脚本一律用quit(),否则会残留一堆后台进程,后面再启动时就可能报端口占用。第二,如果你是从旧教程里复制的find_element_by_id("xxx")写法,在Selenium 4里会直接报错,因为这类分散方法被移除了,必须改成find_element(By.ID, "xxx")这种统一写法。以后的代码我都会用后一种。
3. 元素定位:八个方法用哪个、怎么选
3.1 八种定位方式与它们的性格
页面自动化绕不开“找到元素”这关。Selenium提供了八种定位方式,本质是八种“寻人启事”的写法。我按推荐度从高到低排一下:
| 定位方式 | 写法示例 | 适用场景 |
|---|---|---|
| ID | By.ID, "username" | 元素有唯一id时首选,最稳定 |
| NAME | By.NAME, "email" | 表单控件常用 |
| CLASS_NAME | By.CLASS_NAME, "btn-primary" | 页面样式较规范时可用 |
| TAG_NAME | By.TAG_NAME, "input" | 批量获取同类型元素时用 |
| LINK_TEXT | By.LINK_TEXT, "登录" | 精确定位带文字链接 |
| PARTIAL_LINK_TEXT | By.PARTIAL_LINK_TEXT, "登" | 链接文字很长时用模糊匹配 |
| CSS_SELECTOR | By.CSS_SELECTOR, "#login input.btn" | 写起来优雅,支持层级 |
| XPATH | By.XPATH, "//div[@id='login']//input[1]" | 没有稳定属性时作为兜底方案 |
我的经验是:**能用ID就用ID,没有ID优先看name、>/html/body/div[2]/form/div[3]/button
这种代码就是给自己埋雷。我一般会改成根据属性定位:
from selenium.webdriver.common.by import By driver.find_element(By.XPATH, "//button[contains(@class, 'login-btn')]") driver.find_element(By.XPATH, "//input[@placeholder='请输入用户名']")CSS选择器在复杂页面上更推荐,因为它表达“元素长什么样”比XPath更直观:
driver.find_element(By.CSS_SELECTOR, "form.login-form input[name='password']") driver.find_element(By.CSS_SELECTOR, ".modal .btn-confirm")Selenium 4还加入了相对定位器,比如找“在某个按钮上方的输入框”“在某个标题右侧的链接”,这在测复杂布局时很好用,但起步阶段不用太在意。**定位的核心原则是:优先找开发同学约定加测试专用属性。**我每接手一个项目,都会推动前端在关键交互元素上加># 输入文字 username_input = driver.find_element(By.ID, "username") username_input.clear() username_input.send_keys("tester01") # 点击按钮 login_btn = driver.find_element(By.ID, "login-btn") login_btn.click() # 判断元素是否可见 print(login_btn.is_displayed())
看起来简单,但有几个细节值得注意。send_keys之前要不要clear()?我的习惯是每次都调,因为有些输入框会自带默认值或者上次残留的文字,不先清空就会拼接成一段脏数据。另外,某些定制组件的输入框,send_keys会偶尔丢字符,尤其是中文输入法环境下。这时候可以改用先点击输入框再发送内容,或者直接用JavaScript赋值并触发事件,后者属于高阶技巧,遇到再处理。
下拉框几乎是后台系统标配。处理select标签用Selenium自带的Select类,比硬点option靠谱:
from selenium.webdriver.support.ui import Select city_select = Select(driver.find_element(By.ID, "city")) city_select.select_by_visible_text("北京")复选框、单选框这类元素,click()既能勾选也能取消,注意脚本里要维护“期望状态”和“当前状态”的一致性,否则第二次运行可能点成了反方向。
4.2 iframe和多窗口切换
页面上如果嵌了富文本编辑器、地图组件、第三方支付控件,很大概率会遇到iframe。iframe就是页面里嵌套的另一个完整页面,Selenium的“眼睛”默认只能看到外层页面,看不到嵌套里面的元素。这时候需要先切换再操作:
driver.switch_to.frame("content_iframe") body = driver.find_element(By.TAG_NAME, "body") body.send_keys("这是富文本内容") driver.switch_to.default_content() # 切回外层页面多窗口切换也是高频场景。点击一个链接新开了标签页,Selenium默认还在旧页面,必须手动切换:
driver.switch_to.window(driver.window_handles[-1]) # 操作完新窗口后再切回去 driver.switch_to.window(driver.window_handles[0])**记住一个口诀:切换了就要切回来。**很多脚本跑到一半突然找不到元素,就是因为上一个案例切进了iframe忘记切出,下一个案例还在“迷路”状态。这就是典型的上下文污染。
4.3 上下左右滚动:把元素滚到可见再操作
如果你的页面有无限滚动、横向表格、或者首屏懒加载,滚动操作就是逃不开的环节。最常见的是滚动到页面底部触发加载更多:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight)")更推荐的是直接让某个元素滚动进入视野,再对它操作:
target = driver.find_element(By.XPATH, "//button[contains(text(), '确认提交')]") driver.execute_script("arguments[0].scrollIntoView({block: 'center'});", target) target.click()这里强调“滚到可见再点击”,是因为很多按钮本身没有不可点击,只是屏幕外看不见,浏览器为了模拟真实用户行为会拒绝点击不可见元素,报ElementNotInteractableException。
至于热搜词里的“网页左右滑动”,我一次性说透。横向滚动的场景多见于数据报表、时间轴、横向排列的卡片列表。整个页面横向滚动用window.scrollBy:
driver.execute_script("window.scrollBy(500, 0)")嵌套容器内的横向滚动,比如某个div设置了overflow-x: auto,那要滚动的是这个容器:
driver.execute_script("document.querySelector('.table-wrapper').scrollLeft = 500")实操中我会封装一个函数,传容器选择器和偏移量进去,这样表格组件多点几个页签时,左右滑动就复用了:
def scroll_container(driver, selector, offset): driver.execute_script( f"document.querySelector('{selector}').scrollLeft += {offset}" )4.4 弹窗和文件上传
弹窗分两种。一种是浏览器原生alert,直接处理:
alert = driver.switch_to.alert print("弹窗内容:", alert.text) alert.accept() # 点确定 # alert.dismiss() # 点取消另一种是页面内自定义弹窗,本质上是DOM节点,用普通定位就能找到,往往还要处理一下遮罩层。点击被遮罩挡住时,会报ElementClickInterceptedException,临时解法是先按ESC或点击关闭按钮,再从操作逻辑上解决。
文件上传反而是最省心的场景之一。只要页面上有<input type="file">标签,直接塞路径进去:
driver.find_element(By.CSS_SELECTOR, "input[type=file]").send_keys(r"C:\data\case.xlsx")不要去看什么模拟键盘快捷键,那一套很脆弱。send_keys上传文件是Selenium里少数“简单又可靠”的操作。
5. 等待机制是脚本稳定性的生命线
5.1 三种等待方式到底差在哪
脚本不稳定,最常见的原因就是页面还没准备好,代码就去操作了。Selenium里有三种等待方式,我直接对比一下:
| 等待方式 | 写法 | 作用范围 | 问题 |
|---|---|---|---|
| 强制等待 | time.sleep(3) | 全局,固定阻塞 | 太慢、太脆,能不用就不用 |
| 隐式等待 | driver.implicitly_wait(10) | 所有元素查找过程 | 只解决“元素出现”,不解决“可点击” |
| 显式等待 | WebDriverWait(...).until(...) | 指定条件 | 推荐使用,灵活可控 |
强制等待的问题在于:不等就一定失败,等少了也失败,等多了白白浪费时间。隐式等待的问题是它只会持续轮询“元素存不存在”,很多元素出现的是DOM节点但还没绑定事件、没渲染完,点击一样会出错。所以我的项目中,显式等待是绝对主力。
5.2 显式等待的推荐写法
显式等待的核心思路是:给一个“条件”和“最长等待时间”,Selenium会不断轮询页面,直到条件成立或者超时。这样页面一毫秒加载完,脚本就一毫秒继续执行,快和稳都能兼顾。
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()expected_conditions里常用的条件我整理一张表:
| 条件 | 作用 |
|---|---|
presence_of_element_located | 元素出现在DOM中,但不一定可见 |
visibility_of_element_located | 元素可见(有尺寸、非隐藏) |
element_to_be_clickable | 元素可见且可点击 |
text_to_be_present_in_element | 元素包含指定文本 |
element_to_be_selected | 元素被选中(一般用于下拉项) |
alert_is_present | 弹窗出现 |
invisibility_of_element_located | 元素消失,常用于等待弹层关闭 |
有一点要特别提醒:**别把隐式等待和显式等待混用。**两者的轮询机制会叠加,导致实际等待时间比预期长得多,还会制造一些间歇性失败。我在项目里只设置显式等待,设不设全局隐式等待看团队规范,但同一套代码里两种并存是大忌。
5.3 动态加载页面的等待边界
有些页面是点击按钮后发异步请求,再渲染数据,等待条件不能写死“等待某个文字出现”,因为可能接口报错、数据为空,文本永远不会出现。这时候写断言和等待,应该等待“数据容器出现”,而不是等“具体内容出现”:
WebDriverWait(driver, 10).until( EC.visibility_of_element_located((By.CSS_SELECTOR, ".data-list")) )还有一个实用技巧:等待表格的行数发生变化。比如页面展示“共50条”,你筛选后变成“共5条”,直接等待文本变化比较麻烦,更好的做法是等“分页里的总条数元素”里不再包含原来的数字。这些都是经验活,多做几次项目就有手感了。
6. 从脚本走向框架:用pytest搭建可维护的自动化用例
6.1 别把所有代码堆在一个文件里
脚本写到一定数量,最大的问题不是“写不出来”,而是“改不动”。所有用例堆在一个文件里,每个用例都自己启动浏览器、自己定位、自己关闭,100个用例就是100坨重复代码。改一个按钮的定位,得全局替换几十处。Page Object模式就是来收拾这个局面的。
核心思想不复杂:**把每个页面抽象成一个类,封装该页面上的操作和元素定位,测试用例只关心业务步骤,不关心具体定位表达式。**比如登录页:
class LoginPage: def __init__(self, driver): self.driver = driver def login(self, username, password): self.driver.find_element(By.ID, "username").send_keys(username) self.driver.find_element(By.ID, "password").send_keys(password) self.driver.find_element(By.ID, "login-btn").click()测试用例里调用起来就非常干净:
def test_login_success(login_page): login_page.login("tester01", "123456") assert login_page.is_logged_in() is True页面元素定位被收拢到页面类里,以后前端改了按钮id,只需要改一处。这种模式不是炫技,是让自动化测试从“一次性脚本”变成“可持续维护资产”的分水岭。从第一天就按这个模式写,比写了几百个脚本后再重构成本低得多。
6.2 用pytest管理用例和失败截图
pytest是我用来组织用例的骨架。它和Selenium本身关系不大,但组合起来之后,用例编写、执行、失败处理都变得规范。核心是写一个conftest.py,用fixture管好浏览器生命周期:
import pytest from selenium import webdriver @pytest.fixture(scope="function") def driver(): options = webdriver.ChromeOptions() options.add_argument("--window-size=1920,1080") driver = webdriver.Chrome(options=options) yield driver driver.quit()失败截图是自动化测试最实用的功能之一。没有截图,用例失败就只能靠日志猜;有截图,失败原因一目了然。在conftest.py里加一个hook,每次用例失败自动截图:
@pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield report = outcome.get_result() if report.when == "teardown" and report.failed: driver = item.funcargs.get("driver") if driver: driver.save_screenshot(f"reports/{item.name}.png")这样跑完一个用例集,只需要打开reports目录,就能按用例名看到所有失败现场。
6.3 测试报告、日志与测试数据导出
用例多一点之后,裸的pytest控制台输出就不够用了。我一般加两个东西:日志和报告。日志用Python标准库的logging即可,在关键步骤输出“点击了什么”“输入了什么”“等待了什么”,排查问题会舒服很多。报告方面可以用Allure插件,也可以自己写一个脚本把执行结果汇总。说到底,自动化测试的价值不在于“跑过的用例数量”,而在于“失败的用例能不能在五秒内定位到原因”。
另外很多业务线需要把结果提交给甲方或者领导看,我会用openpyxl把测试结果导出成Excel表格:
import openpyxl wb = openpyxl.Workbook() ws = wb.active ws.append(["用例名", "结果", "耗时", "失败原因"]) for case in results: ws.append([case.name, case.status, case.duration, case.error]) wb.save("reports/auto_test_result.xlsx")6.4 一套框架的实际落地顺序
我建议不要一上来就搭全套框架,而是先跑通一个最痛的核心流程,比如“登录+新建单据+审核通过”。跑通之后,你才清楚这个项目里有哪些定位坑、等待坑、弹窗坑,再回头搭框架时就能带着真实约束来设计。反过来先搭框架再写用例,很容易做出一堆漂亮却跑不动的抽象层。框架是长出来的,不是一开始设计出来的。
7. 被反爬机制误识别成机器人?从这些特征排查
7.1 我们测自己的页面,为什么还被“人机验证”拦
有些团队做了自动化测试之后,发现脚本访问自己的系统时,频繁触发滑块验证或者风控拒绝。这个问题在热搜词里被叫“python selenium反爬虫”,但它本质上不是爬虫问题,而是自动化脚本和真实浏览器存在指纹差异,被前端风控SDK识别出来了。
最常见的自动化特征是navigator.webdriver属性。正常情况下,真实浏览器里这个值是false;由WebDriver驱动的浏览器,这个值默认是true。这是一条非常明显的“机器人标记”。除此之外,自动化脚本的User-Agent往往和真实浏览器不一致,启动参数里带着自动化调试痕迹,行为路径过于机械(比如鼠标动作完全不带轨迹),也会被风控系统标记。
7.2 让脚本少一点“机器人感”的合规调整
这里要先把立场说清楚:下面的方法只适用于你自己开发、自己部署、有权限测试的系统。如果你的自动化测试总是被自家的风控系统拦下,可以通过配置降低误判率。用于绕过别人网站的验证码或风控是不合规的,不在本文讨论范围内。
最基本的调整从启动配置开始:
options = webdriver.ChromeOptions() options.add_argument("--disable-blink-features=AutomationControlled") options.add_experimental_option("excludeSwitches", ["enable-automation"]) options.add_experimental_option("useAutomationExtension", False)--disable-blink-features=AutomationControlled可以移除Chrome内核的自动化标记,上面的excludeSwitches会去掉Chrome顶部的“Chrome正受到自动测试软件控制”提示。这两个参数属于公开、广泛使用的配置。
navigator.webdriver属性还可以通过CDP命令在页面加载前覆盖掉:
driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", { "source": """ Object.defineProperty(navigator, 'webdriver', { get: () => undefined }); """ })这段代码的原理是:在每次导航到新页面、页面JavaScript执行之前,先注入一段脚本,把navigator.webdriver的getter改掉。这样页面加载时读到的就已经是“看似正常”的值。这个方法在自动化测试圈子里很常见,你把它理解为“给测试浏览器化个妆”,让它更容易通过自己系统的风控校验。
7.3 从根源上减少风控误伤
绕来绕去都是治标,真正治本的办法是让测试环境别和线上风控较劲。我的建议分三层:第一,测试环境关闭滑块等强验证能力,或者配置白名单,让测试账号直接绕过;第二,给自动化脚本准备专用的测试账号,走独立的权限和限流策略;第三,推动前端在测试环境增加自动化识别支持,比如识别到WebDriver时自动切换到低压模式。与其不断调整浏览器指纹,不如从系统设计上让自动化测试这件事合法合规地被支持。
另外要提醒一点:如果你连自己公司的系统都过不了风控,优先排查是不是登录态、IP、设备指纹的问题,这类问题靠改浏览器参数解决不了,得从测试账号和测试环境配置入手。自动化测试的边界,永远是自己能控制的系统。
8. 常见异常与排查技巧:一份速查表
8.1 高频报错和它的真实原因
| 异常类型 | 你看到的样子 | 常见原因 | 处理方式 |
|---|---|---|---|
NoSuchElementException | 找不到元素 | 定位表达式错、页面没加载完、在iframe里 | 先等待,再检查iframe,最后验证表达式 |
TimeoutException | 等待超时 | 元素真的没出现,或者条件写错 | 打开失败页面截图,看是元素没渲染还是条件问题 |
ElementClickInterceptedException | 点击被拦截 | 弹窗、遮罩层挡住了元素 | 先关闭弹窗,或用JS点击临时绕过 |
ElementNotInteractableException | 元素不可交互 | 元素隐藏、无尺寸、被禁用 | 检查CSS属性,确认是否真的可见 |
StaleElementReferenceException | 元素引用失效 | 页面刷新后,旧元素引用还在用 | 重新查找元素,不要复用旧对象 |
SessionNotCreatedException | 会话创建失败 | 驱动版本和浏览器版本不匹配 | 去下载匹配的驱动 |
InvalidSelectorException | 选择器无效 | XPath或CSS语法写错 | 在开发者工具里先验证 |
这里特别讲一下StaleElementReferenceException。很多新手遇到这个就懵,其实道理很简单:你把一个元素对象存在变量里,页面发生了刷新或者跳转,原来的DOM节点被替换掉了,旧引用自然失效。解决方法是重新定位后再操作,不要保存“一次性”的旧引用跨页面使用。
8.2 脚本“时好时坏”时先查哪几处
如果脚本不是每次都失败,而是偶发失败,优先检查三件事:第一,是不是存在没清理干净的弹窗,比如上一个用例留下的toast提示挡到了元素;第二,是不是等待时间临界,建议把关键步骤的显式等待判断条件改宽松一点;第三,是不是测试数据串了,前面的用例改了共享数据,后面的用例就受影响。自动化测试里偶发失败经常不是脚本问题,而是测试环境状态问题。在每个用例的setUp里重置数据、清理浏览器上下文,会省掉大量排查痛苦。
8.3 无头模式与下载文件的细节
CI服务器上跑自动化,一般会用无头模式,也就是不弹出浏览器窗口:
options.add_argument("--headless=new")无头模式跑得快,但也有区别。最典型的是页面渲染尺寸、字体加载、PDF下载、文件下载行为都不同。文件下载时,无头模式必须显式配置下载目录:
options.add_experimental_option("prefs", { "download.default_directory": r"D:/reports/downloads", "download.prompt_for_download": False })如果不配置,无头模式的下载不知道存到哪去,测试“导出Excel”这类用例就永远失败。另外无头模式跑一个“点击地图”功能时,我们遇到过窗口太小导致元素渲染不全的问题,后来固定window-size=1920,1080才稳定。能用无头就用无头,但遇到诡异失败时,先切回有头模式跑一遍看现场。
8.4 最后分享一点跨过多次坑之后的体会
做自动化测试这些年,我最深的感受是:**它不是一次性交付的“神器”,而是一套需要持续维护的工程体系。**环境依赖、页面变化、测试数据、网络波动,任何一环出问题都可能让脚本变成摆设。我更推荐从最痛的小用例开始,先解决自己重复劳动最多的一小块业务,跑稳定了,再逐步扩大范围。维护脚本遇到问题时,别急着从网上复制定位表达式,先回来看等待是否合理、iframe是否遗漏、驱动和浏览器版本是否匹配——这三个地方解决了,大部分问题都能落地。
另外,自动化测试的最终价值是让团队有信心做频繁发布。脚本能稳定地在每次发版前跑完核心回归,把失败用例清晰地递到你面前,这件事比“写得很炫酷”重要得多。保持脚本简洁、保持等待机制合理、保持定位符可读,这套体系能陪你走很久。