返回博客

测试前先侦察:让 Playwright 自动化真正可靠的决策树

很多人写 Playwright 脚本的方式是:打开文档,照着示例把 page.click('#submit') 填进去,跑一下,报错,改 selector,再跑,再报错——直到跑通。这个过程有点像蒙着眼睛开车,大方向没错,但每一步都在猜。

我后来总结出一个更可靠的框架,核心只有两点:先侦察,再行动把复杂的服务器管理逻辑包进黑盒,不要让它污染主脚本

决策树:在写第一行代码前先定位

不同的页面类型,需要截然不同的处理策略。在写任何 Playwright 代码之前,先回答两个问题:

第一问:这是静态 HTML 还是动态 Web 应用?

静态 HTML(本地文件、无 JS 渲染)可以直接读文件拿 selector,然后用 file:// URL 打开。这是最简单的路径,省去了起服务器的一切麻烦。

动态应用则需要真实的浏览器渲染才能看到最终 DOM,file:// 不够用,必须起服务器。

第二问:服务器是否已经在跑?

已在跑,直接指向端口即可,脚本只管自动化逻辑。

没在跑,就要考虑服务器生命周期管理——这是最容易把脚本搞乱的部分。

这张决策树把混在一起的问题分开了:「我该怎么访问」和「我该怎么操作」是两件事,不要混写。

侦察阶段:先截图,再找 selector

动态应用真正的坑是:你以为的 DOM 结构和渲染出来的不一样。JS 延迟挂载、骨架屏替换、异步数据填充——凡是没等 JS 执行完就去找 selector,就是在抓幻影。

networkidle 是最粗暴也最有效的等待策略:

page.goto('http://localhost:5173')
page.wait_for_load_state('networkidle')  # 等网络空闲,JS 已执行
page.screenshot(path='/tmp/inspect.png', full_page=True)
content = page.content()

先截图,看一眼当前真实渲染状态;再从 content()locator().all() 里识别 selector。这个「先看,再动手」的习惯,能省掉大量反复调试的时间。

核心原则

侦察阶段的目的不是验证功能,是建立「当前渲染状态的地图」。地图准了,后续的每一步操作才有坐标。

脚本黑盒原则:不要把工具逻辑吃进上下文

这是我在 AI agent 辅助开发里发现的一个反模式:当 agent 需要自动化测试时,它会忍不住先把 with_server.py 读进来,研究实现细节,然后再照着写一遍。结果就是:工具脚本的几百行实现细节全部进了上下文窗口,token 烧了,推理也被噪音干扰。

正确做法是把助手脚本当黑盒对待:--help 先跑一下,看接口,直接调用:

# 单服务器
python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_test.py
 
# 前后端分离
python scripts/with_server.py \
  --server "cd backend && python server.py" --port 3000 \
  --server "cd frontend && npm run dev" --port 5173 \
  -- python your_test.py

主测试脚本只写业务逻辑,服务器的启动、健康检查、关闭全部委托给助手脚本处理。两件事分离,各自保持简单。

这个原则在 AI agent 自动化场景里尤其重要——agent 的上下文窗口是稀缺资源,能用接口解决的,不要用阅读源码解决。

取舍与边界

这套框架的适用范围主要是本地开发和 CI 测试,以及 AI agent 驱动的页面自动化探索。

它不适合替代专门的 E2E 框架设计——selector 稳定性、用例隔离、flaky 治理这些是另一套问题(我在另一篇笔记里单独聊过)。这里解决的是更前置的问题:在不确定目标页面结构的情况下,怎么让第一步操作落地,而不是碰运气。

一句话心法

自动化脚本可靠的前提,是在操作之前先知道自己面对的是什么——侦察不是可选步骤,是必须步骤。