claude-coder 1.5.6 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/setup.js CHANGED
@@ -90,7 +90,7 @@ async function setup() {
90
90
  console.log('============================================');
91
91
  console.log('');
92
92
  console.log(' 第一步: 模型提供商配置');
93
- console.log(' 第二步: MCP 工具 + 调试输出(可选)');
93
+ console.log(' 第二步: MCP 工具配置(可选)');
94
94
  console.log('');
95
95
 
96
96
  // Detect existing config
@@ -336,34 +336,45 @@ async function setup() {
336
336
  if (mcpChoice === 1) {
337
337
  configLines.push('MCP_PLAYWRIGHT=true');
338
338
  log('ok', 'Playwright MCP 已启用');
339
+
340
+ console.log('');
341
+ console.log('请选择 Playwright MCP 浏览器模式:');
342
+ console.log('');
343
+ console.log(' 1) persistent - 懒人模式(默认,推荐)');
344
+ console.log(' 登录一次永久生效,适合 Google SSO、企业内网 API 拉取等日常开发');
345
+ console.log('');
346
+ console.log(' 2) isolated - 开发模式');
347
+ console.log(' 每次会话从快照加载,适合验证登录流程的自动化测试');
348
+ console.log('');
349
+ console.log(' 3) extension - 连接真实浏览器(实验性)');
350
+ console.log(' 通过 Chrome 扩展复用已有登录态和插件');
351
+ console.log(' 需要安装 "Playwright MCP Bridge" 扩展');
339
352
  console.log('');
340
- console.log(' 请确保已安装 Playwright MCP:');
341
- console.log(` ${COLOR.blue}npx @anthropic-ai/claude-code mcp add playwright -- npx @anthropic-ai/playwright-mcp${COLOR.reset}`);
342
- console.log(` ${COLOR.blue}详见: https://github.com/microsoft/playwright-mcp${COLOR.reset}`);
343
- } else {
344
- configLines.push('MCP_PLAYWRIGHT=false');
345
- log('info', '已跳过 Playwright MCP');
346
- }
347
353
 
348
- // Debug output
349
- console.log('');
350
- console.log('是否开启 Claude 调试输出(便于排查问题,输出较多)?');
351
- console.log('');
352
- console.log(' 1) 否 - 静默(默认,推荐)');
353
- console.log(' 2) 是 - verbose(完整每轮输出)');
354
- console.log(' 3) 是 - mcp(MCP 调用,如 Playwright Click)');
355
- console.log('');
354
+ const modeChoice = await askChoice(rl, '选择 [1-3,默认 1]: ', 1, 3, 1);
355
+ const modeMap = { 1: 'persistent', 2: 'isolated', 3: 'extension' };
356
+ const mode = modeMap[modeChoice];
357
+ configLines.push(`MCP_PLAYWRIGHT_MODE=${mode}`);
356
358
 
357
- const debugChoice = await askChoice(rl, '选择 [1-3,默认 1]: ', 1, 3, 1);
358
- configLines.push('', '# Claude 调试(可随时修改)');
359
- if (debugChoice === 2) {
360
- configLines.push('CLAUDE_DEBUG=verbose');
361
- log('info', '已启用 CLAUDE_DEBUG=verbose');
362
- } else if (debugChoice === 3) {
363
- configLines.push('CLAUDE_DEBUG=mcp');
364
- log('info', '已启用 CLAUDE_DEBUG=mcp');
359
+ console.log('');
360
+ if (mode === 'extension') {
361
+ console.log(` ${COLOR.yellow}⚠ 前置条件:安装 Playwright MCP Bridge 浏览器扩展${COLOR.reset}`);
362
+ console.log(` ${COLOR.blue} https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm${COLOR.reset}`);
363
+ console.log('');
364
+ console.log(' 安装扩展后,运行 claude-coder auth 生成 .mcp.json 配置');
365
+ } else if (mode === 'persistent') {
366
+ console.log(' 使用 claude-coder auth <URL> 打开浏览器完成首次登录');
367
+ console.log(' 登录状态将持久保存,后续 MCP 会话自动复用');
368
+ console.log('');
369
+ console.log(' 请确保已安装 Playwright:');
370
+ console.log(` ${COLOR.blue}npx playwright install chromium${COLOR.reset}`);
371
+ } else {
372
+ console.log(' 使用 claude-coder auth <URL> 录制登录状态到 playwright-auth.json');
373
+ console.log(' MCP 每次会话从此文件加载初始 cookies/localStorage');
374
+ }
365
375
  } else {
366
- configLines.push('# CLAUDE_DEBUG=verbose # 取消注释可开启');
376
+ configLines.push('MCP_PLAYWRIGHT=false');
377
+ log('info', '已跳过 Playwright MCP');
367
378
  }
368
379
 
369
380
  // Write config
@@ -378,6 +389,7 @@ async function setup() {
378
389
  console.log(` 配置文件: ${p.envFile}`);
379
390
  console.log(' 使用方式: claude-coder run "你的需求"');
380
391
  console.log(' 详细需求: 创建 requirements.md 后运行 claude-coder run');
392
+ console.log(' 切换模式: claude-coder config mcp <persistent|isolated|extension>');
381
393
  console.log(' 重新配置: claude-coder setup');
382
394
  console.log('');
383
395
  }
package/src/tasks.js CHANGED
@@ -48,7 +48,11 @@ function findNextTask(data) {
48
48
  });
49
49
  })
50
50
  .sort((a, b) => (a.priority || 999) - (b.priority || 999));
51
- return pending[0] || null;
51
+ if (pending.length > 0) return pending[0];
52
+
53
+ const inProgress = features.filter(f => f.status === 'in_progress')
54
+ .sort((a, b) => (a.priority || 999) - (b.priority || 999));
55
+ return inProgress[0] || null;
52
56
  }
53
57
 
54
58
  function setStatus(data, taskId, newStatus) {
package/src/validator.js CHANGED
@@ -3,6 +3,26 @@
3
3
  const fs = require('fs');
4
4
  const { execSync } = require('child_process');
5
5
  const { paths, log, getProjectRoot } = require('./config');
6
+ const { loadTasks, getFeatures } = require('./tasks');
7
+
8
+ function tryExtractFromBroken(text) {
9
+ const result = {};
10
+ const srMatch = text.match(/"session_result"\s*:\s*"(success|failed)"/);
11
+ if (srMatch) result.session_result = srMatch[1];
12
+ const saMatch = text.match(/"status_after"\s*:\s*"(\w+)"/);
13
+ if (saMatch) result.status_after = saMatch[1];
14
+ const sbMatch = text.match(/"status_before"\s*:\s*"(\w+)"/);
15
+ if (sbMatch) result.status_before = sbMatch[1];
16
+ return Object.keys(result).length > 0 ? result : null;
17
+ }
18
+
19
+ function inferFromTasks(taskId) {
20
+ if (!taskId) return null;
21
+ const data = loadTasks();
22
+ if (!data) return null;
23
+ const task = getFeatures(data).find(f => f.id === taskId);
24
+ return task ? task.status : null;
25
+ }
6
26
 
7
27
  function validateSessionResult() {
8
28
  const p = paths();
@@ -12,12 +32,18 @@ function validateSessionResult() {
12
32
  return { valid: false, fatal: true, recoverable: false, reason: 'session_result.json 不存在' };
13
33
  }
14
34
 
35
+ const raw = fs.readFileSync(p.sessionResult, 'utf8');
15
36
  let data;
16
37
  try {
17
- data = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8'));
38
+ data = JSON.parse(raw);
18
39
  } catch (err) {
19
- log('error', `session_result.json 解析失败: ${err.message}`);
20
- return { valid: false, fatal: true, recoverable: false, reason: `JSON 解析失败: ${err.message}` };
40
+ log('warn', `session_result.json 解析失败: ${err.message}`);
41
+ const extracted = tryExtractFromBroken(raw);
42
+ if (extracted) {
43
+ log('info', `从截断 JSON 中提取到关键字段: ${JSON.stringify(extracted)}`);
44
+ return { valid: false, fatal: false, recoverable: true, reason: 'JSON 截断但提取到关键字段', data: extracted };
45
+ }
46
+ return { valid: false, fatal: false, recoverable: true, reason: `JSON 解析失败: ${err.message}` };
21
47
  }
22
48
 
23
49
  // Backward compat: unwrap legacy { current: {...} } format
@@ -33,18 +59,14 @@ function validateSessionResult() {
33
59
  }
34
60
 
35
61
  if (!['success', 'failed'].includes(data.session_result)) {
36
- log('error', `session_result 必须是 success 或 failed,实际是: ${data.session_result}`);
37
- return { valid: false, fatal: true, recoverable: false, reason: `无效 session_result: ${data.session_result}` };
62
+ log('warn', `session_result 必须是 success 或 failed,实际是: ${data.session_result}`);
63
+ return { valid: false, fatal: false, recoverable: true, reason: `无效 session_result: ${data.session_result}`, data };
38
64
  }
39
65
 
40
66
  const validStatuses = ['pending', 'in_progress', 'testing', 'done', 'failed'];
41
67
  if (!validStatuses.includes(data.status_after)) {
42
- log('error', `status_after 不合法: ${data.status_after}`);
43
- return { valid: false, fatal: true, recoverable: false, reason: `无效 status_after: ${data.status_after}` };
44
- }
45
-
46
- if (!data.task_id) {
47
- log('warn', 'session_result.json 缺少 task_id (建议包含)');
68
+ log('warn', `status_after 不合法: ${data.status_after}`);
69
+ return { valid: false, fatal: false, recoverable: true, reason: `无效 status_after: ${data.status_after}`, data };
48
70
  }
49
71
 
50
72
  if (data.session_result === 'success') {
@@ -83,50 +105,56 @@ function checkGitProgress(headBefore) {
83
105
  return { hasCommit: true, warning: false };
84
106
  }
85
107
 
86
- function checkTestCoverage() {
108
+ function checkTestCoverage(taskId, statusAfter) {
87
109
  const p = paths();
88
110
 
89
- if (!fs.existsSync(p.testsFile) || !fs.existsSync(p.sessionResult)) return;
111
+ if (!fs.existsSync(p.testsFile)) return;
112
+ if (statusAfter !== 'done' || !taskId) return;
90
113
 
91
114
  try {
92
- const sr = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8'));
93
115
  const tests = JSON.parse(fs.readFileSync(p.testsFile, 'utf8'));
94
-
95
- const taskId = sr.task_id || '';
96
116
  const testCases = tests.test_cases || [];
97
-
98
- if (sr.status_after === 'done' && sr.tests_passed) {
99
- const taskTests = testCases.filter(t => t.feature_id === taskId);
100
- if (taskTests.length > 0) {
101
- const failed = taskTests.filter(t => t.last_result === 'fail');
102
- if (failed.length > 0) {
103
- log('warn', `tests.json 中有失败的验证记录: ${failed.map(t => t.id).join(', ')}`);
104
- } else {
105
- log('ok', `${taskTests.length} 条验证记录覆盖任务 ${taskId}`);
106
- }
117
+ const taskTests = testCases.filter(t => t.feature_id === taskId);
118
+ if (taskTests.length > 0) {
119
+ const failed = taskTests.filter(t => t.last_result === 'fail');
120
+ if (failed.length > 0) {
121
+ log('warn', `tests.json 中有失败的验证记录: ${failed.map(t => t.id).join(', ')}`);
122
+ } else {
123
+ log('ok', `${taskTests.length} 条验证记录覆盖任务 ${taskId}`);
107
124
  }
108
125
  }
109
126
  } catch { /* ignore */ }
110
127
  }
111
128
 
112
- async function validate(headBefore) {
129
+ async function validate(headBefore, taskId) {
113
130
  log('info', '========== 开始校验 ==========');
114
131
 
115
- let srResult = validateSessionResult();
132
+ const srResult = validateSessionResult();
116
133
  const gitResult = checkGitProgress(headBefore);
117
134
 
118
- // Tiered: has commit + session_result issue → warn, don't rollback good code
119
- if (srResult.recoverable && gitResult.hasCommit) {
120
- log('warn', 'session_result.json 格式异常,但有新提交,降级为警告(不回滚代码)');
121
- } else if (srResult.recoverable && !gitResult.hasCommit) {
122
- log('error', '无新提交且 session_result.json 格式错误,视为致命');
123
- srResult.fatal = true;
124
- }
135
+ let fatal = false;
136
+ let hasWarnings = false;
125
137
 
126
- checkTestCoverage();
138
+ if (srResult.valid) {
139
+ hasWarnings = gitResult.warning;
140
+ } else {
141
+ // session_result.json has issues — cross-validate with git + tasks.json
142
+ if (gitResult.hasCommit) {
143
+ const taskStatus = inferFromTasks(taskId);
144
+ if (taskStatus === 'done' || taskStatus === 'testing') {
145
+ log('warn', `session_result.json 异常,但 tasks.json 显示 ${taskId} 已 ${taskStatus},且有新提交,降级为警告`);
146
+ } else {
147
+ log('warn', 'session_result.json 异常,但有新提交,降级为警告(不回滚代码)');
148
+ }
149
+ hasWarnings = true;
150
+ } else {
151
+ log('error', '无新提交且 session_result.json 异常,视为致命');
152
+ fatal = true;
153
+ }
154
+ }
127
155
 
128
- const fatal = srResult.fatal;
129
- const hasWarnings = gitResult.warning || srResult.recoverable;
156
+ const statusAfter = srResult.data?.status_after || inferFromTasks(taskId) || null;
157
+ checkTestCoverage(taskId, statusAfter);
130
158
 
131
159
  if (fatal) {
132
160
  log('error', '========== 校验失败 (致命) ==========');
@@ -51,8 +51,8 @@
51
51
  | `.claude-coder/session_result.json` | 本次会话的结构化输出 | 每次会话结束时覆盖写入 |
52
52
  | `.claude-coder/tests.json` | 功能验证记录(轻量) | 可新增和更新;仅当功能涉及 API 或核心逻辑时记录 |
53
53
  | `.claude-coder/test.env` | 测试凭证(API Key、测试账号等) | **可追加写入**;发现测试需要的凭证时持久化到此文件 |
54
- | `.claude-coder/playwright-auth.json` | 登录状态快照(备份参考) | 只读;由 `claude-coder auth` 生成 |
55
- | `.claude-coder/browser-profile/` | 持久化浏览器 Profile | MCP 自动维护;首次需手动登录,之后永久保持 |
54
+ | `.claude-coder/playwright-auth.json` | 浏览器登录状态快照(isolated 模式时由 `claude-coder auth` 生成) | 只读;persistent/extension 模式下此文件不存在 |
55
+ | `.mcp.json` | MCP 服务配置(由 `claude-coder auth` 自动生成) | **只读,绝对不得修改** |
56
56
 
57
57
  ### requirements.md 处理原则
58
58
 
@@ -98,12 +98,9 @@
98
98
  ```json
99
99
  {
100
100
  "session_result": "success | failed",
101
- "task_id": "feat-xxx",
102
101
  "status_before": "pending | failed",
103
102
  "status_after": "done | failed | in_progress | testing",
104
- "git_commit": "abc1234 null",
105
- "tests_passed": true | false,
106
- "notes": "本次会话的简要说明"
103
+ "notes": "本次做了什么 + 遇到的问题 + 给下一个会话的提醒"
107
104
  }
108
105
  ```
109
106
 
@@ -138,38 +135,17 @@
138
135
 
139
136
  ## 任务状态机(严格遵守)
140
137
 
141
- 每个任务在 `tasks.json` 中有一个 `status` 字段,合法状态和迁移规则如下:
138
+ 每个任务在 `tasks.json` 中有一个 `status` 字段,合法迁移路径如下:
142
139
 
143
- ```
144
- pending ──→ in_progress ──→ testing ──→ done
145
-
146
-
147
- failed ──→ in_progress(重试)
148
- ```
149
-
150
- ### 状态说明
151
-
152
- | 状态 | 含义 | 何时设置 |
140
+ | 当前状态 | 可迁移至 | 触发条件 |
153
141
  |---|---|---|
154
- | `pending` | 未开始 | 初始状态 |
155
- | `in_progress` | 正在实现 | 你开始编码时 |
156
- | `testing` | 代码已写完,正在测试 | 代码完成、开始验证时 |
157
- | `done` | 测试通过,功能完成 | 端到端测试通过后 |
158
- | `failed` | 测试失败或实现有问题 | 测试未通过时 |
159
-
160
- ### 迁移规则(铁律)
161
-
162
- - `pending` → `in_progress`:开始工作
163
- - `in_progress` → `testing`:代码写完,开始验证
164
- - `testing` → `done`:所有测试通过
165
- - `testing` → `failed`:测试未通过
166
- - `failed` → `in_progress`:重试修复
167
-
168
- **禁止的迁移**:
169
- - `pending` → `done`(不允许跳步)
170
- - `pending` → `testing`(必须先写代码)
171
- - `in_progress` → `done`(必须先测试)
172
- - 任何状态 → `pending`(不允许回退到未开始)
142
+ | `pending` | `in_progress` | 开始编码 |
143
+ | `in_progress` | `testing` | 代码写完,开始验证 |
144
+ | `testing` | `done` | 所有测试通过 |
145
+ | `testing` | `failed` | 测试未通过 |
146
+ | `failed` | `in_progress` | 重试修复 |
147
+
148
+ **禁止**:跳步(如 `pending` → `done`)、回退到 `pending`、未测试直接 `done`
173
149
 
174
150
  ---
175
151
 
@@ -248,18 +224,15 @@ pending ──→ in_progress ──→ testing ──→ done
248
224
  1. **后台服务管理**:根据 prompt 提示决定——单次模式(`--max 1`)时停止所有后台服务(`lsof -ti :端口 | xargs kill`);连续模式时保持服务运行,下个 session 继续使用
249
225
  2. **按需更新文档和 profile**:
250
226
  - **README / 用户文档**:仅当对外行为变化(新增功能、API 变更、使用方式变化)时更新
251
- - **架构 / API 文档**:如果本次新增了模块、改变了模块职责或新增了 API 端点,更新 `existing_docs` 中对应的架构或 API 文档。同时更新 `project_profile.json` 的 `existing_docs` 列表(若新增了文档文件)
227
+ - **项目指令文件**:如果本次新增了模块、改变了模块职责或新增了 API 端点,更新 `.claude/CLAUDE.md`。同时确保 `project_profile.json` 的 `existing_docs` 列表包含此文件
252
228
  - **profile 补全**:如果 prompt 中提示 `project_profile.json` 有缺陷(如 services 为空、existing_docs 为空),在此步骤补全。Harness 依赖 profile 做环境初始化和上下文注入
253
229
  3. **Git 提交**:`git add -A && git commit -m "feat(task-id): 功能描述"`
254
230
  4. **写入 session_result.json**(notes 要充分记录上下文供下次恢复):
255
231
  ```json
256
232
  {
257
233
  "session_result": "success 或 failed",
258
- "task_id": "当前任务 ID",
259
234
  "status_before": "任务开始时的状态",
260
235
  "status_after": "任务结束时的状态",
261
- "git_commit": "本次提交的 hash",
262
- "tests_passed": true 或 false,
263
236
  "notes": "本次做了什么 + 遇到的问题 + 给下一个会话的提醒"
264
237
  }
265
238
  ```
@@ -20,8 +20,8 @@
20
20
 
21
21
  **文档标准(按优先级)**:
22
22
  1. **README.md**(必须有):项目简介、技术栈、目录结构、如何运行。若缺失或过于简略,先补充
23
- 2. **架构文档**(推荐有):如果 `docs/` 中没有架构概述,生成一份简要的架构文档(如 `docs/ARCHITECTURE.md`),包含:模块职责、核心数据流、关键 API 路由。格式用结构化标题,方便 AI 快速检索
24
- 3. **API 文档**:如果项目有 API 且无文档,在 docs/ README 中补充主要端点列表
23
+ 2. **`.claude/CLAUDE.md`**(推荐有):检查 `.claude/` 目录下是否已有 `CLAUDE.md`。若无,生成一份项目指令文件,采用 WHAT/WHY/HOW 格式:WHAT(项目是什么、技术栈)、WHY(关键技术决策)、HOW(开发命令、测试命令、关键路径表、编码规则)。此文件会被 Claude Code 自动加载为项目上下文
24
+ 3. **API 文档**:如果项目有 API 且无文档,在 `.claude/CLAUDE.md` HOW 部分或 README 中补充主要端点列表
25
25
 
26
26
  按顺序检查以下文件,**存在则读取**,不存在则跳过:
27
27
 
@@ -42,7 +42,7 @@
42
42
  2. 根据需求(`requirements.md` 或 harness 传入的需求文本),设计技术架构
43
43
  3. 创建项目目录结构和基础文件(入口文件、配置文件、依赖文件等)
44
44
  4. 生成 `README.md`(项目用途、技术栈、如何运行)
45
- 5. 如果项目包含 2 个以上模块或前后端分离,生成简要架构文档 `docs/ARCHITECTURE.md`(模块职责、数据流、API 路由)
45
+ 5. 如果 `.claude/CLAUDE.md` 不存在,生成项目指令文件(WHAT/WHY/HOW 格式),包含模块职责、数据流、API 路由、开发和测试命令
46
46
  6. 初始化包管理(`npm init` / `pip freeze` 等)
47
47
  7. 完成后,执行**步骤 2A 的扫描流程**生成 `project_profile.json`
48
48
 
@@ -100,7 +100,7 @@
100
100
  "python_env": "conda:env_name | venv | system",
101
101
  "node_version": "20 | 18 | none"
102
102
  },
103
- "existing_docs": ["README.md", "docs/api.md"],
103
+ "existing_docs": ["README.md", ".claude/CLAUDE.md"],
104
104
  "has_tests": false,
105
105
  "has_docker": false,
106
106
  "mcp_tools": {
@@ -0,0 +1,158 @@
1
+ # Playwright 自动化测试通用规则 v0.0.1
2
+
3
+ ## 一、四条铁律
4
+
5
+ 1. **真实操作** — 必须通过 Playwright MCP 产生浏览器交互,代码审查不等于测试
6
+ 2. **测试业务** — 断言基于用户可见结果(页面文本、按钮状态),非内部变量
7
+ 3. **独立可重复** — 每个场景不依赖其他测试结果
8
+ 4. **先调查再修复** — 失败先分析根因,不要修改测试让它通过
9
+
10
+ ## 二、三步测试方法论
11
+
12
+ 任何 Web 项目的端到端测试遵循三步走:
13
+
14
+ ### Step 1: 功能验证(Happy Path)
15
+
16
+ 核心用户流程能走通,每个步骤对应一个 Playwright MCP 工具调用:
17
+
18
+ ```
19
+ 1. browser_navigate → [页面URL]
20
+ 2. browser_snapshot → 确认页面加载,定位关键元素 ref
21
+ 3. browser_fill_form / browser_type → 输入测试数据
22
+ 4. browser_click → 提交操作
23
+ 5. browser_wait_for → 等待结果出现
24
+ 6. browser_snapshot → 验证预期结果
25
+ ```
26
+
27
+ ### Step 2: 错误场景(Unhappy Path)
28
+
29
+ | 类别 | 典型场景 |
30
+ |------|---------|
31
+ | 输入验证 | 空提交、超长输入、特殊字符、非法格式 |
32
+ | 认证权限 | 未登录访问、过期凭证、无效 API Key |
33
+ | 网络服务 | 后端宕机、慢响应、API 500 |
34
+ | 状态边界 | 空数据、大数据量、重复提交、浏览器后退 |
35
+
36
+ ### Step 3: 探索性测试
37
+
38
+ 以目标用户角色自由使用系统,关注可发现性、可理解性、响应速度、错误恢复、视觉一致性。
39
+
40
+ ## 三、Playwright MCP 工具速查
41
+
42
+ ### 导航与观察
43
+
44
+ | 工具 | 用途 | 关键参数 |
45
+ |------|------|---------|
46
+ | `browser_navigate` | 打开页面 | `url` |
47
+ | `browser_snapshot` | 获取页面可访问性快照 | 无 |
48
+ | `browser_console_messages` | 检查控制台 | `level` |
49
+ | `browser_network_requests` | 网络请求日志 | 无 |
50
+
51
+ ### 交互操作
52
+
53
+ | 工具 | 用途 | 关键参数 |
54
+ |------|------|---------|
55
+ | `browser_click` | 点击元素 | `ref`, `element` |
56
+ | `browser_type` | 逐字符输入 | `ref`, `text`, `submit` |
57
+ | `browser_fill_form` | 批量填写表单 | `fields[]` |
58
+ | `browser_select_option` | 选择下拉项 | `ref`, `values[]` |
59
+ | `browser_press_key` | 按键 | `key` |
60
+ | `browser_file_upload` | 上传文件 | `paths[]` |
61
+ | `browser_handle_dialog` | 处理弹窗 | `accept` |
62
+
63
+ ### 等待与控制
64
+
65
+ | 工具 | 用途 | 关键参数 |
66
+ |------|------|---------|
67
+ | `browser_wait_for` | 等待元素/文本出现 | `text`, `ref`, `timeout` |
68
+ | `browser_evaluate` | 执行 JS | `function` |
69
+ | `browser_close` | 关闭页面 | 无 |
70
+
71
+ ## 四、Smart Snapshot 策略(节省 40-60% Token)
72
+
73
+ 每次 `browser_snapshot` 消耗 3,000-8,000 tokens。分级控制:
74
+
75
+ | 级别 | 何时 snapshot | 示例 |
76
+ |------|-------------|------|
77
+ | **必须** | 首次加载页面 | navigate 后确认页面正确 |
78
+ | **必须** | 关键断言点 | 验证操作结果出现 |
79
+ | **必须** | 操作失败时 | 调查页面状态 |
80
+ | **可选** | 中间操作后 | fill 后确认文字填入 |
81
+ | **跳过** | 连续同类操作间 | 连续选择多个下拉框 |
82
+ | **跳过** | 等待循环中 | 改用 `browser_wait_for` |
83
+
84
+ **高效模式**:navigate → snapshot → fill → select → click → wait_for → snapshot(**2 次**)
85
+ **低效模式**:navigate → snapshot → fill → snapshot → select → snapshot → click → snapshot(**4 次**)
86
+
87
+ ## 五、等待策略
88
+
89
+ ### 按操作类型选择
90
+
91
+ | 操作类型 | 策略 | Token 消耗 |
92
+ |---------|------|-----------|
93
+ | 瞬时(导航、点击) | 直接操作,不等待 | 极低 |
94
+ | 短等(表单提交) | `browser_wait_for text="成功" timeout=10000` | ~5K |
95
+ | 长等(AI 生成、文件处理) | 指数退避轮询 | ~20K |
96
+ | 超长等(批量处理) | Shell 端 API 检查 + 最终 1 次 snapshot | ~5.5K |
97
+
98
+ ### 指数退避轮询模式(长操作)
99
+
100
+ - 每步 snapshot → 合并 2-3 操作后再 snapshot
101
+ - MCP 做 20+ 步 → 长流程用 Playwright CLI
102
+ - 反复 navigate 同一页面 → 在同一页面完成
103
+ - 失败后盲目重试 → 先 `browser_console_messages` 分析
104
+
105
+ ### 优先级映射
106
+
107
+ P0(核心流程)必测 → P1(错误处理)必测 → P2(次要功能)按需 → P3 低优先
108
+
109
+ 预算 >200K: P0+P1+P2 | 100-200K: P0+P1 | <100K: 仅 P0
110
+
111
+ ## 六、凭证管理
112
+
113
+ `.mcp.json` 由 `claude-coder auth` 自动生成,根据配置模式使用不同参数:
114
+ - persistent(默认):`--user-data-dir=<path>`,登录态自动保持
115
+ - isolated:`--isolated --storage-state=<path>`,每次从快照加载
116
+ - extension:`--extension`,连接真实浏览器
117
+
118
+ 凭证失效时:不修改 auth 文件,报告中标注,提示用户运行 `claude-coder auth [URL]`。
119
+
120
+ ## 七、失败处理
121
+
122
+ **阻断性**(立即停止): 服务未启动、500 错误、凭证缺失、页面空白
123
+
124
+ **非阻断性**(记录继续): 样式异常、console warning、慢响应
125
+
126
+ 失败时: snapshot(记录状态)→ console_messages(错误日志)→ 停止该场景 → 继续下一个
127
+
128
+ ## 八、tasks.json 测试步骤模板
129
+
130
+ ```json
131
+ {
132
+ "steps": [
133
+ "【规则】阅读 .claude-coder/test_rule.md",
134
+ "【环境】curl [后端]/health && curl [前端](失败则停止)",
135
+ "【P0】Playwright MCP 执行核心 Happy Path(Smart Snapshot)",
136
+ "【P1】错误场景:空输入、无效凭证",
137
+ "【记录】结果写入 record/",
138
+ "【预算】消耗 >80% 时跳过低优先级,记录 session_result.json"
139
+ ]
140
+ }
141
+ ```
142
+
143
+ ## 九、测试报告格式
144
+
145
+ ```markdown
146
+ # E2E 测试报告
147
+ **日期**: YYYY-MM-DD | **环境**: 前端 [URL] / 后端 [URL]
148
+
149
+ | 场景 | 结果 | 备注 |
150
+ |------|------|------|
151
+ | [名称] | PASS/FAIL | [简要] |
152
+
153
+ ## 发现的问题
154
+ ### [P0/P1/P2] 标题
155
+ - **复现**: [Playwright 动作序列]
156
+ - **预期/实际**: ...
157
+ - **根因**: [代码分析]
158
+ ```