Playwright 全量 E2E 测试(任意 Web 管理端)
铁律(必须遵守)
- 用户说"用 playwright 测试 X / E2E 测一下 X"时,直接开测,不做二次确认:不问测试范围、不问数据副作用、不问要不要补充场景。
- 默认按全量执行:完整核心链路 + 所有分支场景 + 边界探测(校验拦截类),含写操作(测试环境造数据、创建/发布/结束均允许)。
- 若用户给了测试用例(飞书 wiki/docx 链接或贴文),用 lark-cli(
--as bot)拉取后逐条映射执行;没有用例则按下方"全量用例设计模板"自行设计。 - 测完必须生成测试报告 .md + 截图留证 + 可复用脚本,present_files 给用户。
- 数据收尾:能推终态的推终态(如发布+提前结束→已失效);无法废弃的残留列进报告。record-only,只找 bug 不改代码。
- 账号密码:使用使用者提供的测试账号;首次拿到后存入其 memory,后续会话直接复用,不要在报告/截图元数据里明文扩散。
0. 环境引导(arm64 Ubuntu22 沙盒,无 sudo,会话可能重置)
# 一次性安装(约2-4分钟)
pip install playwright --break-system-packages -q
python3 -m playwright install chromium # --with-deps 不可用,别用
# 缺 libxdamage1 时:普通用户解包 deb(apt-get download 自动选对架构)
mkdir -p ~/deps && cd ~/deps
apt-get download libxdamage1 && dpkg -x libxdamage1*.deb ~/deps/xd
# 之后所有脚本运行都要带:
LD_LIBRARY_PATH=$HOME/deps/xd/usr/lib/aarch64-linux-gnu python3 xxx.py
连通性:目标站可能是内网/VPN 环境。先 curl -s -o /dev/null -w "%{http_code}" 探测,不通则提示用户开 VPN;偶发瞬断(000)用 safe_goto 重试 3 次,每次 sleep 8s。
沙盒硬限制:单次 bash 超时上限约 178 秒;nohup & 后台进程活不过单次调用。长套件必须分片:公共函数抽 _fw2.py,按场景拆 _chunks.py V|S4|S5...,一片一条命令跑,结果追加同一 JSON。pkill 匹配串要用 [_]x 防自杀,且别和启动命令写在同一条里。
截图注意:宿主 Read 工具可能读不了 PNG(Unsupported Image),别浪费轮次重试,用 DOM dump(evaluate 取 innerText/结构)代替可视化,截图仅供用户查看。
1. 登录:anji-captcha 滑块验证码自动破解
适用于后端为 anji-captcha(blockPuzzle)的站点:验证码服务可能与主站不同域(拦截 /captcha/get 的 response 即可,无需关心部署位置),响应嵌套在 data.repData(部分系统为顶层 repData),图片 base64 无 data: 前缀,含 secretKey。账号密码由使用者提供。
captcha 求解库 _captcha_lib.py(可直接抄):
import base64, io, random
import numpy as np
from PIL import Image
def load_b64(b64): # 无前缀的裸 base64
return Image.open(io.BytesIO(base64.b64decode(b64))).convert("RGBA")
def find_gap_x(orig, piece):
"""灰带法:缺口被半透明灰覆盖(低饱和+中亮度),行限定在拼图块bbox内,按列统计找左沿"""
O = np.array(orig).astype(int); P = np.array(piece).astype(int)
H, W = O.shape[:2]
A = P[..., 3]; ys, xs = np.where(A > 10)
if len(ys) < 50: return None
py0, ph = int(ys.min()), int(ys.max()-ys.min()+1)
if P.shape[0] != H:
s = H / P.shape[0]; py0, ph = int(py0*s), int(ph*s)
ry0, ry1 = py0, min(py0+ph, H)
mx = O[...,:3].max(2); mn = O[...,:3].min(2)
val = (O[...,0]+O[...,1]+O[...,2])/3
grayish = ((mx-mn) < 28) & (val > 80) & (val < 215)
cs = np.convolve(grayish[ry0:ry1].sum(0), np.ones(3)/3, "same")
thr = max(3, int((ry1-ry0)*0.30)); x = 5
while x < W-60:
if cs[x] >= thr:
run = x
while run < W-1 and cs[run] >= thr*0.6: run += 1
if abs((run-x) - 46) <= 16: return int(x), 46, (ry0, ry1) # 46≈块宽,按实际 bbox 算
x = run + 3
else: x += 1
return None
def find_gap_x_edge(orig, piece):
"""兜底:缺口左右沿强垂直边缘,G[x]+G[x+pw] 双峰配对"""
O = np.array(orig.convert("RGB")).astype(int); P = np.array(piece.convert("RGBA"))
H, W = O.shape[:2]; A = P[...,3]; ys, xs = np.where(A>10)
if len(ys) < 50: return None
py0, ph = int(ys.min()), int(ys.max()-ys.min()+1)
if P.shape[0] != H:
s = H/P.shape[0]; py0, ph = int(py0*s), int(ph*s)
g = np.abs(np.diff(O[py0:py0+ph], axis=1)).sum(2).sum(0)
gs = np.convolve(g, np.ones(5)/5, "same")
pw = int(xs.max()-xs.min()+1); best, bx = -1, None
for x in range(10, W-pw-10):
sc = gs[x] + gs[min(x+pw, W-1)]
if sc > best: best, bx = sc, x
return (int(bx), pw, (py0, py0+ph)) if bx else None
def human_track(dist):
"""easeOut 22~34 步 + 竖向抖动,收尾精确对准"""
tr, t, cur = [], 0, 0.0
steps = random.randint(22, 34)
for i in range(1, steps+1):
nxt = dist * (1 - (1 - i/steps) ** 2.6)
t += random.randint(28, 60)
tr.append((nxt-cur, random.uniform(-1.2, 1.2), t)); cur = nxt
tr.append((dist-cur, 0, t+random.randint(60,140)))
return tr
登录循环要点:填账号密码(按 placeholder 定位;注意登录按钮可能是 div 而非 button)→ 点登录 → 等 .verifybox 可见(有延迟,别双击登录键,会 toggle 关掉弹窗)→ 拦 /captcha/get response 取图 → find_gap_x 失败则 find_gap_x_edge → 拖动:handle=.verify-move-block,dist=(gap_x-px0)*scale-(sub.x-panel.x),px0=拼图块在画布内的左留白,scale=panel宽/原图宽 → 鼠标按下后逐步 mouse.move+短 wait,松开前停 150-200ms。部分部署服务端容差仅 ±1px,循环重试 6 次,失败后点 .verify-refresh 换图。验证成功标志:/captcha/check repCode=0000 或 URL 离开 /login。
2. 会话复用(token cookie 注入)
多数管理端登录态是会话级 Cookie(浏览器一关就没;launch_persistent_context 也保不住,每次新开要注入)。登录成功后把 token Cookie 名/值存到文件(如 auth_token.txt),后续:
TOKEN = open("auth_token.txt").read().strip()
ctx = p.chromium.launch_persistent_context("<workdir>/pw-profile", headless=True,
viewport={"width":1600,"height":900}, args=["--no-sandbox"])
ctx.add_cookies([{"name":"<token Cookie名>","value":TOKEN,
"domain":"<站点域名>","path":"/","expires":4102444800}])
token 失效(跳回 /login)就重跑验证码登录流程。
3. 页面探查(写脚本前必做)
performance.getEntriesByType('resource')列 JS chunk;可疑交互直接curl下载 chunk 搜中文文案/函数体读业务逻辑(比黑盒猜快得多)。- dump
.el-form-item(label + 内部 input/checkbox/radio + checked 状态)、按钮、表头、表格行,搞清控件结构再写步骤。 - 静默失败(无 toast 无跳转无报错)排查三板斧:hook
window.fetch+XHR.open记录出站请求;提交后 1 秒内抓 toast(3 秒自动消失);检查是否弹了自定义.el-dialog(如风控确认)。
4. Element Plus 通用操作模式(实战验证)
- 定位输入框:
.el-form-item:has-text("标签") input,多个取.nth(i)(同一表单项内可含多输入框!) - 点纯文本(勾选/词条):
text="减钱"精确匹配防 strict mode 多元素报错;单选组用.el-form-item:has-text("组标签") .el-radio:has-text("选项") - 日期:
inputclick → Ctrl+A → type "2026-08-25 12:00:00" → Enter;开始时间必须晚于当前时间(常见校验),每次填用datetime.now()+110s现算,别用脚本启动时的全局值 - toast:
.el-message;表单校验错:.el-form-item__error;确认框:.el-message-box→ 按实际文案点按钮(可能是"确定发布"而非"确定") - 选择面板(el-dialog/el-drawer 通用):优先
.el-table__row .el-checkbox勾选,词条类选cursor:pointer的叶子元素(过滤掉 取消/确定/搜索/标题),确认按钮试 "确 定/确定/确认/保存" - 表格行操作:按行文本匹配后在 evaluate 里
tr.querySelectorAll('button')按文案 click(避开 strict mode) - 新建页务必从列表页点按钮进入:提交成功后前端常
router.go(-1),直接 goto 表单页会回退到空白页 - 全程挂 console/pageerror/response(≥400) 三监听,错误计入报告
5. 全量用例设计模板(无外部用例时)
按序覆盖,每步截图(编号命名),结果写 JSON:
- 列表:URL/表头/初始数据
- 搜索:名称精确过滤、重置恢复、状态 tab 过滤(校验每行状态列)
- 新建-校验批(一张表单连测):空提交必填拦截 → 逐字段非法值(过短/超限/业务规则,探测不到拦截就是发现)
- 新建-正向:默认组合创建 → 列表可查 → 状态初始值
- 分支场景逐个:类型切换(注意来回切换是否丢状态)/多档位/各种"指定 XX"(空选拦截 + 面板选择正向)/标签用户/叠加规则
- 编辑:仅未发布可编辑,改名生效
- 发布:二次确认弹窗 → 状态流转(未发布→未开始),到点后转进行中(开始时间设 now+2min,轮询等)
- 提前结束:→已失效
- 删除能力:有无按钮(没有就记入发现)
- 控制台/HTTP 0 错误
6. 报告格式(输出 <功能名>E2E测试报告.md)
# XX E2E 测试报告(Playwright)
被测地址/测试时间/账号/工具/总体结论(PASS x/y,发现 n 项)
## 一、测试范围与结果(分轮表格: 用例|结果|关键证据)
## 二、发现清单(F编号|级别P1-P3或记录|描述|证据截图)
## 三、核心专项(如风控/状态机,引用实测文案)
## 四、实测业务规则汇总(编号列表,源码+实测双证)
## 五、测试数据残留(记录ID|名称|状态|说明)
## 六、可复用资产(脚本清单+重跑命令)
## 七、建议(修复/产品确认项)
发现定性要克制:行为≠缺陷,拿不准的标"记录/待产品确认";用户质疑时做对照实验复核(如"勾选A+空B可保存"可能是"空B=未配置"的设计)。
7. 业务系统通用速查
- 二次确认弹窗按钮按实际文案点(如"确定发布"、"确认风险并保存"),别假设是"确定"
- 含指定商品/风险价的活动,创建/发布时可能弹风控确认弹窗,静默失败时优先排查
- 开始时间已过的未发布活动,常见限制:禁止发布且无删除入口——残留数据记入报告
- 各环境的账号密码、弹窗文案、已测页面的选择器优先查使用者 memory,没有再现场探查