<!-- title: 拆webapp-testing：说明书教你别读代码 -->

# 拆webapp-testing：说明书教你别读代码

## 概述

本栏目此前盘点测试类 Skills 时，曾感叹这条赛道是间「空屋」——大量测试件无人认领、装了没人用。但空屋的门后其实一直住着一件官方货：Anthropic example-skills 里的 **webapp-testing**。它是官方全家桶里最不起眼、也最值得拆的一件：全文不到一百行，却有整整两段在阻止模型读代码；它不用一张图、不写一句废话，把「上下文窗口是预算」这件事执行到了字节级。本文基于 2026-09-19 从本机 Claude Code 插件缓存加载的 SKILL.md 当前版本全文，逐段拆到行，最后给出可直接抄走的七条写法。

## 评测表

| 维度 | 权重 | 评分 | 依据 |
|------|------|------|------|
| 功能完整度 | 25% | ★★★★☆ | 本地 Web 测试闭环齐：起服、侦察、动作、日志全覆盖；缺断言框架与测试报告输出 |
| 设计质量 | 25% | ★★★★★ | 上下文经济学的教科书：黑盒条款、决策树前置、军令状注释，无一处冗余 |
| 实战效果 | 20% | ★★★★☆ | 直击模型写浏览器脚本最高频的翻车点（不等 networkidle 就读 DOM）；长期数据未经跟踪 |
| 上手难度 | 15% | ★★★★★ | 零配置项，装好 Playwright 即用；无密钥、无账号、无环境变量 |
| 社区活跃度 | 15% | ★★★☆☆ | 随官方 example-skills 插件包整包分发更新，无独立仓库与独立 star |

加权总分约 4.3 / 5。短板集中在「社区活跃度」——它压根没有自己的社区，它是基础设施。

## 三条路线对比：它到底替你省了什么

同样的「让 Claude 测网页」，市场上有三条路。先把它们摆在一起，后面拆的每一行设计才有参照物：

| 路线 | 形态 | 上下文开销 | 会话状态 | 适合场景 |
|------|------|-----------|---------|---------|
| Playwright MCP 服务器 | 每个动作一次工具调用 | 高：每步截图/DOM 都回灌对话 | 有，跨调用保持 | 交互式探索、人机协同调试 |
| 裸写 Playwright 脚本 | Claude 从零手写脚本 | 低，但翻车率高 | 无 | 简单页面 |
| **webapp-testing** | 一次 Bash 调用跑完整个脚本 | 最低：产物落盘不回灌 | 无需 | 本地功能流验证、bug 复现 |

MCP 路线的问题是账单：点十个按钮就是十次工具往返，每次往返的页面状态都挤进上下文。Skill 路线反着来——Claude 把十步动作写进一个 Python 文件，一次执行，只把最终结论带回对话。**这是「每步一个工具」和「一步一个脚本」的架构分歧**，也是这件 Skill 存在的全部理由。

## 逐段拆解：不到一百行的九个心机

### 第一行就是命令，没有一句铺垫

全文开篇：

> To test local web applications, write native Python Playwright scripts.

翻译过来八个词：测本地 Web 应用，写原生 Python Playwright 脚本。注意三件事：第一，它是祈使句，动词开头，模型读到第一行就知道自己该「做什么动作」；第二，`native Python Playwright` 三个定语一口气锁死技术栈，不给模型发挥空间——不写 Selenium，不写 requests，不问要不要装 Cypress；第三，全文没有任何「This skill aims to...」式的自我介绍。对比大量社区 SKILL.md 开头两段讲背景讲愿景，这件的第一行已经在干活。**省下来的每一行，都是上下文预算。**

### Helper Scripts 声明 + 全文最狠的一条

紧接着是资源声明和这段罕见的指令：

> **Always run scripts with `--help` first** to see usage. DO NOT read the source until you try running the script first and find that a customized solution is abslutely necessary.

一个大写的 DO NOT。这份说明书用最重的语气告诉读者：**别读我附带的代码**。理由写在下一句：

> These scripts can be very large and thus pollute your context window. They exist to be called directly as black-box scripts rather than ingested into your context window.

这是整份文件的设计内核：SKILL.md 的作者非常清楚模型有个本能——拿到代码就想读一遍再动手。而这个「附带的 with_server.py」是管服务器生死的长脚本，读一遍几千 token 就没了，读完还未必用得上。于是作者把规矩写死：先跑 `--help` 看用法，直接当黑盒调；只有跑不通、确认必须魔改时才准打开源码。

顺带一个细节：这句里 `abslutely` 是拼错的（应为 absolutely）。官方文件、人手写、没过一遍拼写检查——反而说明这不是模板批量生成的货。

### ASCII 决策树：图都不画，因为读者是模型

文件的核心段是一棵纯文本决策树：

```
User task → Is it static HTML?
    ├─ Yes → Read HTML file directly to identify selectors
    │         ├─ Success → Write Playwright script using selectors
    │         └─ Fails/Incomplete → Treat as dynamic (below)
    │
    └─ No (dynamic webapp) → Is the server already running?
        ├─ No → Run: python scripts/with_server.py --help
        │        Then use the helper + write simplified Playwright script
        │
        └─ Yes → Reconnaissance-then-action:
            1. Navigate and wait for networkidle
            2. Take screenshot or inspect DOM
            3. Identify selectors from rendered state
            4. Execute actions with discovered selectors
```

三个观察。其一，决策树压在正文最前面——**先选路，再动手**，模型不会顺着惯性一路写到底。其二，它用 ASCII 字符而不用 Mermaid：SKILL.md 的读者是模型，纯文本零渲染损耗，在终端、diff、任何查看器里都不会坏。其三，注意这一版**没有**「When to Use / When Not to Use」章节——取舍判断没有写成散列的清单，而是全部折叠进了这棵树：静态走读文件，动态走起服务器，已在跑就别重复起。路径即文档。

把这棵树画成流程图，是这样的：

```mermaid
flowchart TD
    A["用户：测一下本地网页"] --> B{"页面是静态 HTML？"}
    B -->|是| C["直接读文件找选择器"]
    C --> D{"能找到？"}
    D -->|能| E["写 Playwright 脚本"]
    D -->|找不到| F["按动态页面处理"]
    B -->|否| G{"服务器已在跑？"}
    F --> G
    G -->|否| H["with_server.py 托管起服"]
    G -->|是| I["先侦察：截图加读 DOM"]
    H --> I
    I --> J["从渲染结果定选择器"]
    J --> K["执行动作出结论"]
    E --> K
```

### with_server.py 示例：命令即文档

```bash
python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_automation.py
```

单服务器一行，双服务器就再加一组 `--server` 加 `--port`，最后用 `--` 分隔符接上真正要跑的自动化脚本。这个设计的聪明处：**示例本身就是可执行命令**。模型不需要「理解」with_server.py 的接口设计，把示例里的字符串换掉就能跑。文档的终点不是被读懂，而是被复制。

### 脚本模板里藏着军令状

给出的 Python 模板不长，但注释全是强调级：

```python
browser = p.chromium.launch(headless=True) # Always launch chromium in headless mode
page.goto('http://localhost:5173') # Server already running and ready
page.wait_for_load_state('networkidle') # CRITICAL: Wait for JS to execute
```

三行注释三种力度：`Always` 是常规军令，`Server already running and ready` 是解释「为什么你的脚本里不用管起服」，而 `CRITICAL` 是大写警报。这里有个容易被忽略的机制：**模型写自动化脚本时会照抄这个模板，注释会跟着一起被复制进最终产物**。也就是说，这三句提示词不会停留在 SKILL.md 里被人读完就忘——它们跟着代码走，直到浏览器真正关上为止。注释即持久化的提示词，这是全文最高级的一处设计。

### 侦察优先：产物落盘，结论上会话

「Reconnaissance-Then-Action」三步：先 `page.screenshot(path='/tmp/inspect.png', full_page=True)` 和 `content = page.content()` 摸清渲染后的 DOM，再从侦察结果里挑选择器，最后才执行动作。注意截图路径写死在 `/tmp`——侦察的原始产物全部落盘，**进上下文的只有「找到了哪个按钮」这个结论**。和 MCP 路线每步截图回灌对话一对照，两条路线对上下文的态度高下立判。

### 坑位只写一个，但用 ❌✅ 写成一对

> ❌ **Don't** inspect the DOM before waiting for `networkidle` on dynamic apps
> ✅ **Do** wait for `page.wait_for_load_state('networkidle')` before inspection

全文唯一的 Common Pitfall，只写了模型最常翻的那个车：动态页面 JS 还没执行完就去读 DOM，拿到一个空壳。没有罗列十条注意事项稀释注意力——**坑位清单越长，每条坑越不被当真**。一条坑，反例正例各一句，完事。

### Best Practices 首条重复黑盒条款

五条最佳实践，第一条又是「Use bundled scripts as black boxes」。开头说过一遍的话，结尾再说一遍——这不是啰嗦，是赌模型读到尾部时已经忘了开头。重要的事写两遍，首尾夹住中间。

### 尾挂三个参考文件：第三层延迟加载

```
- examples/element_discovery.py - Discovering buttons, links, and inputs on a page
- examples/static_html_automation.py - Using file:// URLs for local HTML
- examples/console_logging.py - Capturing console logs during automation
```

每个文件一行名字加一行用途，仅此而已。于是整份 Skill 形成三层渐进披露：**SKILL.md 正文（必读）→ examples 示例（按需读）→ scripts 源码（原则上永远别读）**。上下文花费逐层可控，这正是 Agent Skills 官方工程博客里「少装元数据、按需加载内容」思想落在一个具体文件上的样子。

把这份文件的解剖结构收进一张图：

```mermaid
mindmap
  root((SKILL.md 解剖))
    首句祈使句
      八词锁死技术栈
    黑盒条款
      先跑 help
      禁读源码
    ASCII决策树
      静态动态先分流
      起服归脚本管
    命令即文档
      换字符串就能跑
    军令状注释
      headless必开
      CRITICAL等networkidle
    单坑成对写
      反例正例各一句
    三层延迟加载
      examples按需读
```

## 写法排行榜：七条可抄经验，按可迁移性排序

1. **黑盒条款**——凡是 Skill 附带脚本，写明「先 `--help`、禁读源码、理由是防上下文污染」。任何带脚本资源的 Skill 都该抄。
2. **首句祈使句**——第一行就是动词命令，锁死技术栈，零铺垫。
3. **决策树前置**——把分流逻辑做成树放在正文最前，先选路再动手；终端场景用 ASCII 树。
4. **军令状注释**——把关键约束写进代码模板注释里，让它跟着复制粘贴进入最终产物。
5. **产物落盘**——侦察类中间产物写死临时目录，只让结论进上下文。
6. **单坑成对**——Pitfall 只写最高频的一个，❌✅ 各一句，不稀释。
7. **无铺垫开头**——删掉所有「本 Skill 旨在」，背景介绍是上下文里的 pure cost。

## 适用与不适用

**适合交给它**：本地 dev server 的功能流验证；前后端双进程起服再测联调；纯静态 HTML 小工具的选择器定位；复现 bug 后验证修复是否生效；用 console 日志定位前端 JS 报错。

**不要交给它**：已上线公网站点的长期回归——那是 CI 里正规测试框架的活，Skill 每次现写脚本，不可复用为资产；需要多轮人机交互的探索式调试——会话态场景 MCP 更合适；纯逻辑单元测试——不起浏览器的事别拉浏览器；性能压测——工具定位完全不符。

**安装与使用**：

```bash
# 方式一：官方仓库手动装（目录名以仓库实际为准，clone 后找 webapp-testing）
git clone https://github.com/anthropics/skills.git
cp -r skills/example-skills/webapp-testing ~/.claude/skills/

# 方式二：skills.sh CLI 先搜再装
npx skills find webapp-testing

# 依赖（Skill 本体之外，一次性）
pip install playwright
playwright install chromium
```

装好后用自然语言触发即可：「帮我测一下 localhost:3000 的登录流程」「这个表单提交后没反应，复现一下」。也可以在本机已有的 example-skills 插件包里直接调用。

## 结语

webapp-testing 拆到底，只有一件事：**它把上下文窗口当预算来花，而且花得极其抠门**。首句八词、决策树零渲染、命令即文档、注释即军令、产物落盘、源码拉黑——一百行不到，每一行都在替模型省 token 或者防翻车。测试赛道或许仍是生态里的空屋，但官方在这间空屋里放的，是一件值得所有 Skill 作者抄作业的样板间。

## 参考来源

1. [anthropics/skills 官方 Skills 仓库](https://github.com/anthropics/skills) — webapp-testing 的上游仓库，位于 example-skills 分类下
2. 本机 Claude Code 插件缓存（anthropic-agent-skills / example-skills 包）内 webapp-testing 的 SKILL.md 全文 — 本文所有原文引用均出自 2026-09-19 加载的当前版本
3. [Equipping agents for the real world with Agent Skills — Anthropic Engineering](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills) — 渐进披露与元数据设计的官方阐述
4. [Playwright for Python 官方文档](https://playwright.dev/python/) — sync_playwright、networkidle 等本文涉及 API 的权威定义

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
