那次回归开始得很像一次普通的测试维护。工作台的一批页面刚完成改版,Python 测试先跑完了,143 项全部通过;但浏览器视觉阶段却停在一个页面上,等待卡片出现的判断一直没有满足,最后超时。表面看,像是浏览器测试太严格,或者前端只是换了一个写法,测试还抱着旧结构不放。
我一开始也有过这个念头:既然业务代码已经通过了,能不能把等待时间拉长一点,或者把“必须找到卡片”改成“页面没有报错就算通过”?但这类改法会把问题藏起来。回归测试失败时,最先要确认的不是选择器该不该改,而是页面仍不仍然履行原来的契约。
失败表面像选择器过时
这次失败的直接表现很具体:浏览器阶段等待页面卡片,结果卡片数量是0;其他自动化检查没有先给出明显的 Python 层错误。一个元素找不到,确实可能意味着 DOM 结构已经合理变化,也可能意味着页面根本没有按约定把内容渲染出来。两者在错误信息里长得很像,处理方式却完全相反。
如果不区分这两种情况,最容易出现的后果是把真实退化包装成测试修复。测试可能从“必须有统一入口、必须能打开内容”变成“页面能加载就行”,视觉检查也可能从固定行为退回到只看有没有报错。这样做短期会让红灯变绿,后面却会让用户在页面上找不到播放入口、看不到评论徽标,甚至无法进入原本应该存在的审看流程。
所以我先把失败拆成两个问题:页面输出的内容是否正确,测试寻找内容的方式是否仍然合理。只有前一个答案为“是”,后一个答案才值得进入选择器维护。
先找页面应该守住什么
我回到共享页面规范,而不是直接打开测试文件改定位。这个工作台的卡片不是各页面随意拼出来的装饰,它们要经过统一的媒体墙,才能获得一致的播放入口、评论标识和审看行为。某个页面在改版时改成直接生成卡片,虽然屏幕上可能仍有标题和图片,但它已经绕过了这条共享链路。
这就是为什么卡片数量会是0,或者为什么等待函数会一直等不到预期结果。问题不在于测试不知道新卡片叫什么,而在于页面没有进入测试所验证的那套页面契约。DOM 结构只是契约的外在形状,真正需要先确认的是用户还能不能完成原来的动作。
我选择先恢复共享媒体墙,是因为它同时补回了多个行为:统一的播放入口、评论徽标、卡片生成方式和后续审看队列。若只在页面上临时加一个测试能找到的类名,能够修补单个断言,却会继续留下功能分叉。测试也许不再报错,用户看到的却仍是一个不完整的页面。
恢复页面行为后,我重新检查了缓存读取、内容深链和关闭审看窗口等路径。这一步的意义在于,不把“卡片出现”误当成整个功能已经恢复。卡片只是入口,入口后面的打开、返回和关闭同样属于契约。如果只盯着首屏截图,深链失效这种问题很容易在回归中漏掉。
再判断 DOM 变化是否合理
页面恢复之后,浏览器回归又暴露出另一类失败:有一个内容页确实已经合理改成原生按钮,点击后直接打开共享审看窗。旧的内联点击方式和旧弹窗结构不再存在。这个变化不是为了迁就测试临时做的,而是前端行为本身已经完成了迁移,新的按钮语义更清楚,也更容易被键盘和辅助技术理解。
这时我才进入选择器判断。旧测试还在寻找旧弹窗的 .dlg.open 和内联 onclick,它们找不到并不代表页面坏了,而是测试还在描述一个已经退出的实现。只有当页面的用户行为保持不变、DOM 变化有明确理由且旧结构确实不再存在时,更新选择器才是修复,而不是放水。
我只更新了2处 DOM 选择器,范围刻意保持很小。没有顺手删除旧断言,也没有把所有“找不到元素”的检查改成宽松匹配。选择器改变的是测试进入行为的路径,不应该改变测试要证明的事情:内容能被打开,深链仍然成功,缓存仍然能读取,旧审看窗口在需要时仍然可以关闭。
不要用阈值掩盖契约缺口
这类失败最容易诱发几个错误捷径。第一个是放宽等待阈值,认为30秒不够就改成60秒;第二个是把卡片数量从“必须大于0”降成“可以为0”;第三个是删除视觉阶段的断言,只保留页面没有崩溃。它们都能减少失败次数,却没有回答页面为什么没有卡片。
我没有这么做,是因为等待时间解决不了渲染入口缺失,阈值也不能证明页面具备统一播放和评论行为。如果真正的问题是加载变慢,应该从加载时序和服务状态入手;如果是契约被绕过,就要恢复共享链路;如果只是结构迁移,才改定位方式。不同原因不能共用一个“多等一会儿”的处方。
回归过程中还遇到过短暂的服务拒绝连接,以及新增脚本的语法和检查错误。我把它们分别处理,没有为了避开波动而修改测试范围,也没有把出错页面从批量检查中排除。一个流程是否可靠,不看它顺利时多快,而看它遇到暂时失败时能不能保持判断边界。
用行为链而不是单个节点验收
这次之后,我把页面回归理解成一条行为链:页面加载出内容,内容进入共享入口,用户能够打开,打开后能通过深链抵达目标,缓存数据可复用,窗口能够正常关闭;视觉上还要在规定宽度下保持布局稳定。选择器只是这条链上的一个观察点,不能代替整条链。
实际排查时,我会先记录失败发生在哪一层。若内容数量为0,先查页面是否走了共享渲染入口;若数量正常但点击失败,再查按钮语义和事件绑定;若打开成功但返回异常,再查深链和关闭行为。这样拆分以后,测试文件不再是第一个被修改的地方,页面契约才是第一处需要核对的证据。
多宽度验收也不能省略。页面修复后,视觉阶段通过了12个布局检查,随后继续检查多个页面和宽度,确认首屏没有因为某个区域变高而把下一排入口挤出可视范围。一个宽度下能看到卡片,不等于所有常用宽度下都能正常使用。布局测试的价值,正在于把这种“看起来偶尔不对”的问题固定下来。
这个方法以后可以复用在任何浏览器回归里:先把失败节点放回用户动作链,再判断它是内容缺失、页面行为退化、服务波动,还是 DOM 语义变化。只有最后一种,并且行为契约已经被新的实现稳定承接时,才修改选择器。
下次我会怎么做
下次遇到“Python 测试通过、浏览器阶段失败”,我会先做一次最小契约核对:页面有没有真实内容,内容有没有经过共享入口,统一播放和评论行为是否还在,缓存、深链、打开和关闭是否完整。这个核对不需要先改代码,却能快速排除“测试太严格”这种未经证明的判断。
如果发现页面绕过了共享链路,我会先恢复页面功能,再重跑原有测试。若发现页面确实换成了更合理的原生控件,我会只改受影响的选择器,并在回归记录里写明旧结构为何不再存在、新结构承接了什么行为。选择器改动的数量本身不是质量证明,能否保留原行为断言才是。
最后,我会保留多宽度布局检查,并把暂时的服务故障、代码检查错误和真实页面退化分开记录。测试的职责不是把所有变化都挡在门外,也不是替所有变化开绿灯,而是帮助我判断哪些变化值得接受、哪些变化必须回滚。 这件事让我重新意识到,真正成熟的回归流程,不是让页面永远不变,而是让每一次变化都能说清楚:契约变了吗,行为还在吗,测试为什么要跟着变。