网页自动化最让人误判的故障,往往不是页面彻底打不开,而是页面看起来还能用,脚本却突然不会用了。前几天我遇到的就是这种情况:一个原本用于自动出图的网页流程,在页面改版后失效。人工打开页面、点几下控件,任务仍然可以完成;但原来的自动化链路已经无法按预期选择模式和模型。
我一开始很容易把它理解成“网页改了,所以把选择器改一下”。后来沿着现场信息往下看,才发现问题不在某个按钮消失,而在控件展示文案换了说法。原来的模式名称被改成了“图片生成”,原来用于识别模型的“图片 4.0”也换成了“Seedream”或“Seedream 4.0”。功能没有消失,自动化契约却把显示文字误当成了永久身份。
网页自动化真正应该依赖的是控件语义,而不是某一次页面上显示出来的句子。
页面还能手动完成,不等于自动化已经恢复
这次最容易造成错觉的地方,是人工操作仍然有效。只要直接在网页里点击,新名称很快就能找到对应功能,出图任务也能继续。表面上看,这似乎只是脚本暂时落后于页面,临时手动完成就算把事情救回来了。
但手动完成只能证明服务还在,不能证明自动化契约还成立。脚本真正需要确认的是:它能否找到目标模式,能否识别目标模型,能否把调用方传入的旧名称转换成当前页面接受的名称,并且能否在不执行真实生成的情况下判断提交前状态。如果这些问题没有答案,下一次运行仍然会在同一个位置停住。
如果不处理,后面会导致两个问题。第一,任何依赖这条链路的任务都会继续随机失败,失败原因还可能被误判成网络问题或服务不可用。第二,团队会逐渐形成一个危险习惯:页面改版就临时人工绕过,等有空再修脚本。绕过次数一多,自动化就只剩下“平时看起来能跑”,没有稳定的边界。
先看现场证据,再判断改的是名称还是能力
我没有一开始就把旧字符串替换成新字符串,而是先核对现场的 DOM 和可访问性信息。原因很简单:同样是选择失败,可能有几种完全不同的原因。控件可能被移走,可能被拆成新的层级,也可能只是文字变了。如果不先确认,直接改代码,很容易把页面结构问题误当成文案问题。
这次现场证据指向得很清楚。模式控件仍然承担原来的选择作用,只是显示名称从旧模式名变成了“图片生成”;模型控件也仍然指向原来的免费基线,只是出现了“Seedream”和“Seedream 4.0”等新文案。能力没有被替换,语义也没有改变,改变的是对外展示层。
这里最容易误判的是,把“新名字”和“新能力”混为一谈。名字变了,不代表业务对象变了;反过来,名字没变,也不代表背后的能力没有调整。只有把现场看到的结构、控件职责和提交前状态放在一起,才能判断这次到底是选择器漂移,还是功能契约发生了变化。
我选择先保留证据再改映射,是因为页面文案属于最容易变化的一层,而控件语义属于更值得稳定下来的那一层。先确认这两层的关系,后面才能让修复兼容变化,而不是只追着这一次改版打补丁。
别名不是把旧代码再堆一遍
确定是文案改名后,最短的错误捷径是把旧字符串换成新字符串。比如把“Agent 模式”直接改成“图片生成”,把“图片 4.0”直接改成“Seedream 4.0”。这样可能让当前页面恢复,但旧调用方仍然会失效,下一次页面再改名时,又要从业务逻辑里找一遍所有硬编码。
我更愿意把这看成一个规范化问题。外部输入可以接受多个显示名称,内部逻辑只使用一个稳定的规范名。模式选择器保留新旧文案作为别名,模型选择器也同时认识旧名称、短名称和带版本的新名称;这些别名进入选择流程后,先统一映射到规范名,再交给后面的校验和提交前检查。
这样做的关键不在于“多写几个字符串”,而在于把变化隔离在边界上。调用方暂时传入旧模型名,不需要立刻全部改完;页面当前显示新模型名,选择器也能识别;业务逻辑继续围绕规范名判断,不会在每个分支里重复处理同义词。
别名层的价值,是把不稳定的显示文案挡在系统边缘,让业务逻辑只面对稳定的语义。
这里仍然要注意一个边界:别名只能表示已经被现场证据确认的同一语义,不能把相似名字都放进同一个桶里。带版本的模型名、短名称和旧名称可以在确认对应关系后归一化;一个看起来相近但能力不同的模型,则必须继续区分。兼容性不是越宽越好,宽到失去区分能力,反而会把错误选择藏起来。
规范名之后,还要保留失败的可见性
选择器恢复后,我没有把所有未知名称都静默归到默认模型。那样看似更“坚韧”,实际会让脚本在页面再次变化时选错对象。一个可靠的兼容层应该能处理已知别名,也应该在遇到未登记的文案时明确失败,让诊断信息告诉我:页面出现了什么,当前支持什么,哪个语义没有被确认。
这次修复把模型名称的归一化放在进入业务流程的边界处。页面侧读取到新名称后,先转成规范名;旧调用方传入旧名称时,也走同一条归一化路径。之后的参数检查、模板选择和提交前预检,都不再各自判断“图片 4.0”还是“Seedream 4.0”。同一件事只在一个地方解释,失败也更容易定位。
我一开始以为只要选择器能点中就够了,后来发现还必须验证归一化后的结果有没有贯穿链路。否则可能出现一种假修复:页面被点开了,但后续参数仍携带旧名;或者预检显示成功,真正提交时又因为模型值不一致而失败。
因此,测试不能只测“找到元素”,还要测“旧输入和新输入最后得到同一个规范结果”。这类测试很小,却直接守住了这次改动的核心判断。它不关心页面今天的按钮排在哪个位置,而关心不同显示文案是否仍然指向同一个语义对象。
用离线测试和只读预检证明链路恢复
验证分成了两层。第一层是离线测试,确认旧名称仍可用、新名称能够识别、规范化结果一致,并且未知名称不会被无声吞掉。测试还保留了旧文案场景,避免修复新页面时把已有调用方一起删掉。
第二层是只读预检。它真正打开页面,读取现场控件和当前可选项,但停在提交前,不触发真实生成。预检需要回答几个具体问题:是否进入正确的图片模式,是否读到当前模型控件,调用方请求的旧名称是否被解析为新的规范名,最终状态是否允许提交。
这次预检显示,旧模型请求能够解析到“Seedream 4.0”,模式也能进入“图片生成”,并且没有提交任务。这个结果比“人工点成功了”更有价值,因为它同时证明了现场页面、选择器别名、规范化逻辑和提交前边界是连通的。
如果只跑离线测试,可能会错过页面真实结构的变化;如果只做真实生成,又会把验证和实际用量绑在一起。最稳妥的验证顺序,是先用离线测试守住语义规则,再用零用量只读预检确认页面现场,最后把动作停在不会产生外部副作用的位置。
还有一个容易漏掉的环节,是知识或状态记录的更新。代码修好不代表维护记录自动变新。这次在完成修复后又做了状态复核,确认页面漂移已经被处理,而不是只根据文件修改时间就宣布“已验证”。这一步看起来不像写代码,却决定了下一位维护者能不能相信这条结论。
下次我会怎么做
下次再遇到网页改版,我会先问自己:变化的是能力、结构,还是展示文案?如果现场证据证明只是文案变化,我会按“收集新旧名称—确认同一语义—登记别名—统一规范化—测试双向兼容—只读预检”的顺序处理,而不是先改一个字符串试运行。
我会把选择器分成两层看。外层负责适应网页展示,允许有限、明确的别名;内层负责稳定业务语义,只接受规范名。遇到没有证据支持的新名称时,让它显式失败并留下诊断,比猜一个默认值更安全。
我也会把“人工能做完”与“自动化已修复”分开记录。前者说明服务还活着,后者必须有测试、现场预检和清楚的验证结果。这样做的好处不是让脚本永远不受改版影响,而是让每次改版都能在边界处被发现、被解释、被修复。
这件事让我重新意识到,稳定的自动化不是把页面冻结在某个日期,而是承认页面会变化,并把变化集中在可检查的适配层里。一个流程成熟的标志,不是它能在今天认出某个按钮,而是明天按钮换了名字之后,我们仍然知道它代表什么、哪里需要改,以及怎样证明改动没有越过提交边界。