@autobest-ui/agent 1.0.28 → 1.0.29
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.
|
@@ -27,6 +27,93 @@ npm start
|
|
|
27
27
|
|
|
28
28
|
配套 Skill 位于 `../../skills/common/trace-recorder/`,由 `autobest-agent-sync common` 安装,并作为 `skill://trace-recorder/SKILL.md` MCP 资源提供。Skill 的 `agents/openai.yaml` 声明了对 `trace-mcp-recorder` 的工具依赖。
|
|
29
29
|
|
|
30
|
+
### Skill 如何使用
|
|
31
|
+
|
|
32
|
+
Skill 不是需要单独运行的服务。完成 MCP 配置并重启 Codex 后,在对话中使用 `$trace-recorder` 或直接描述录制意图,Codex 会按照 Skill 规则调用 `trace-mcp-recorder` 的 MCP 工具。
|
|
33
|
+
|
|
34
|
+
启动示例:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
$trace-recorder 启动 Playwright trace,访问 https://cpd.dev.autobestdevops.com,version=v2.1.0,title=首页功能02,operator=张三
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
也可以不写 Skill 名称:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
请打开 https://cpd.dev.autobestdevops.com 进行 Trace 录制,版本 v2.1.0,标题为首页功能02,操作人张三
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Codex 会调用 `start_trace_recording`,打开可见 Chromium。浏览器打开后:
|
|
47
|
+
|
|
48
|
+
1. 默认使用 PC viewport `1400×740`;需要移动端时点击“切换移动端”。
|
|
49
|
+
2. 点击“开始录制”,服务会启动 Trace、临时禁用缓存并刷新页面,等待页面资源稳定。
|
|
50
|
+
3. 页面稳定后手动操作。普通点击、输入和滚动不会生成自定义步骤。
|
|
51
|
+
4. 需要保留某个页面状态时,点击“画笔”标注,再点击“截图”;截图步骤显示为 `步骤N | 页面标题`。
|
|
52
|
+
5. 录制完成后点击“停止录制”,或在 Codex 中发送停止命令。
|
|
53
|
+
|
|
54
|
+
停止示例:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
$trace-recorder 停止录制
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
首次停止会生成 trace 并询问是否上传:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
$trace-recorder 上传录制内容
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
也可以一步完成:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
$trace-recorder 停止录制并上传
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Skill 会把上传返回的回放链接以及版本、标题、操作人、浏览器版本整理返回。若用户只点击页面上的“停止录制”,服务会自动上传,随后在 Codex 中发送“获取回放链接”即可取回结果。
|
|
73
|
+
|
|
74
|
+
### MCP 工具直接调用
|
|
75
|
+
|
|
76
|
+
需要绕过自然语言 Skill 时,可直接调用两个 MCP 工具:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"name": "start_trace_recording",
|
|
81
|
+
"arguments": {
|
|
82
|
+
"url": "https://cpd.dev.autobestdevops.com",
|
|
83
|
+
"version": "v2.1.0",
|
|
84
|
+
"title": "首页功能02",
|
|
85
|
+
"operator": "张三"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
停止并先询问上传:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"name": "stop_trace_recording",
|
|
95
|
+
"arguments": {}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
确认上传:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"name": "stop_trace_recording",
|
|
104
|
+
"arguments": { "confirm": "upload" }
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
取消上传:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"name": "stop_trace_recording",
|
|
113
|
+
"arguments": { "confirm": "cancel" }
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
30
117
|
## 环境变量
|
|
31
118
|
|
|
32
119
|
| 变量 | 默认值 | 说明 |
|
|
@@ -34,16 +121,18 @@ npm start
|
|
|
34
121
|
| `BACKEND_BASE_URL` | 无,必填 | 私有后端基础地址,必须为 HTTP(S) URL |
|
|
35
122
|
| `MAX_RECORD_DURATION` | `1800000` | 最大录制时长,单位毫秒;超时后自动停止并上传 |
|
|
36
123
|
|
|
124
|
+
页面初始导航、录制开始后的刷新以及路由恢复导航超时均为 1 小时;`MAX_RECORD_DURATION` 只控制录制时长,不控制页面导航。
|
|
125
|
+
|
|
37
126
|
回放链接按 `${BACKEND_BASE_URL}/trace-viewer/?trace=${fileUrl}` 生成。服务地址、域名和端口均来自 `BACKEND_BASE_URL`。
|
|
38
127
|
|
|
39
128
|
## MCP 工具
|
|
40
129
|
|
|
41
|
-
- `start_trace_recording`:参数为必填 `url` 和可选 `version`、`title`、`operator`。该工具打开页面,默认 PC
|
|
130
|
+
- `start_trace_recording`:参数为必填 `url` 和可选 `version`、`title`、`operator`。该工具打开页面,默认 PC 端;点击左下角按钮可在 PC 与 iPhone 14 Pro Max(430×932、DPR 3、触摸、iPhone User-Agent)之间来回切换。切换可以发生在录制过程中,当前页面会立即调整为对应 viewport,后续截图按当前模式生成。点击“开始录制”后才真正启动 Trace。`url` 独立确定被测页面;`title` 是录制标题或功能描述,也可以直接填写 `bug3452` 这类 Bug 编号。系统不额外传递独立的 Bug 字段。MCP 认证上下文中的 `operator`、`name` 或 `preferred_username` 优先于参数。
|
|
42
131
|
- `stop_trace_recording`:首次调用不传参数,只停止 trace 并询问是否上传;用户确认后传 `confirm=upload`,取消时传 `confirm=cancel`。上传成功后返回回放链接及版本、标题、操作人、浏览器版本。
|
|
43
132
|
|
|
44
133
|
录制超时后服务自动停止并自动上传(无需人工确认),随后第一次人工调用 `stop_trace_recording` 会取回自动停止的结果;再调用则返回没有活动会话。
|
|
45
134
|
|
|
46
|
-
Trace
|
|
135
|
+
Trace 在点击页面左下角“开始录制”后先临时禁用缓存并刷新页面,等待资源稳定后再开始计时;点击“停止录制”会停止并自动上传,随后可通过 `stop_trace_recording` 取回回放链接。设备切换和录制控制按钮不会写入操作步骤;新打开的页面会继承当前设备模式。普通点击、输入、滚动、动画和图片加载不会生成自定义步骤,只有点击“截图”才会创建一个 `tracing.group()`,组名为 `步骤N | 页面标题`,组内保存当前页面截图并使用 `waitForTimeout(1)` 推进官方 Viewer 播放。画笔使用 SVG 路径覆盖层,截图时会带上标注;退出画笔会清空已有标注。连续 2 分钟没有页面活动时自动停止并上传。官方 Viewer 的具体显示可能是 Group 标签或 Group 内嵌 screenshot/Wait 节点,取决于 Viewer 版本。
|
|
47
136
|
|
|
48
137
|
## 后端接口契约
|
|
49
138
|
|
|
@@ -13,6 +13,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
13
13
|
import { z } from 'zod';
|
|
14
14
|
|
|
15
15
|
const DEFAULT_MAX_RECORD_DURATION = 1_800_000;
|
|
16
|
+
const PAGE_NAVIGATION_TIMEOUT = 3_600_000;
|
|
16
17
|
const USER_ACTIVITY_DEBOUNCE_DELAY = 800;
|
|
17
18
|
const IDLE_STOP_DELAY = 120_000;
|
|
18
19
|
const DESKTOP_VIEWPORT = { width: 1400, height: 740 };
|
|
@@ -313,10 +314,12 @@ function createRecorderPageScript() {
|
|
|
313
314
|
toolbar.addEventListener('pointercancel', stopDragging);
|
|
314
315
|
renderAnnotation();
|
|
315
316
|
|
|
316
|
-
|
|
317
|
+
let currentDeviceMode = 'desktop';
|
|
318
|
+
const render = (state, deviceMode) => {
|
|
317
319
|
const recording = state === 'recording';
|
|
318
320
|
const waiting = state === 'waiting_for_start';
|
|
319
321
|
const preparing = state === 'starting';
|
|
322
|
+
currentDeviceMode = deviceMode ?? currentDeviceMode;
|
|
320
323
|
annotationEnabled = recording;
|
|
321
324
|
if (!annotationEnabled) penEnabled = false;
|
|
322
325
|
renderAnnotation();
|
|
@@ -326,7 +329,7 @@ function createRecorderPageScript() {
|
|
|
326
329
|
deviceButton.disabled = state === 'starting' || state === 'stopping' || state === 'uploading';
|
|
327
330
|
startButton.textContent = preparing ? '准备页面' : recording ? '录制中' : '开始录制';
|
|
328
331
|
stopButton.textContent = state === 'stopping' || state === 'uploading' ? '正在停止' : '停止录制';
|
|
329
|
-
deviceButton.textContent =
|
|
332
|
+
deviceButton.textContent = currentDeviceMode === 'mobile' ? '移动端已启用' : '切换移动端';
|
|
330
333
|
};
|
|
331
334
|
const control = async action => {
|
|
332
335
|
startButton.disabled = true;
|
|
@@ -336,7 +339,7 @@ function createRecorderPageScript() {
|
|
|
336
339
|
const result = await globalThis.__traceRecorderControl?.(action);
|
|
337
340
|
render(result?.state, result?.deviceMode);
|
|
338
341
|
} catch {
|
|
339
|
-
|
|
342
|
+
render('waiting_for_start');
|
|
340
343
|
}
|
|
341
344
|
};
|
|
342
345
|
startButton.addEventListener('click', event => {
|
|
@@ -455,7 +458,7 @@ export class TraceRecorder {
|
|
|
455
458
|
await context.addInitScript({ content: createRecorderPageScript() });
|
|
456
459
|
}
|
|
457
460
|
page = await context.newPage();
|
|
458
|
-
await page.goto(targetUrl);
|
|
461
|
+
await page.goto(targetUrl, { timeout: PAGE_NAVIGATION_TIMEOUT });
|
|
459
462
|
|
|
460
463
|
const metadata = {
|
|
461
464
|
version: sessionInput.version,
|
|
@@ -661,7 +664,7 @@ export class TraceRecorder {
|
|
|
661
664
|
cacheWasDisabled = true;
|
|
662
665
|
}
|
|
663
666
|
|
|
664
|
-
await session.page.reload({ waitUntil: 'domcontentloaded' });
|
|
667
|
+
await session.page.reload({ waitUntil: 'domcontentloaded', timeout: PAGE_NAVIGATION_TIMEOUT });
|
|
665
668
|
// networkidle is intentionally best-effort: SPAs with polling/websockets
|
|
666
669
|
// may never become idle, while the resource checks below remain bounded.
|
|
667
670
|
if (typeof session.page.waitForLoadState === 'function') {
|
|
@@ -686,7 +689,12 @@ export class TraceRecorder {
|
|
|
686
689
|
}
|
|
687
690
|
}
|
|
688
691
|
if (currentUrl && typeof session.page.url === 'function' && session.page.url() !== currentUrl) {
|
|
689
|
-
await session.page.goto(currentUrl, { waitUntil: 'domcontentloaded' });
|
|
692
|
+
await session.page.goto(currentUrl, { waitUntil: 'domcontentloaded', timeout: PAGE_NAVIGATION_TIMEOUT });
|
|
693
|
+
}
|
|
694
|
+
// A reload can reset parts of Chromium's emulation state. Re-apply the
|
|
695
|
+
// selected device after all navigation and resource loading has finished.
|
|
696
|
+
if (session.deviceMode === 'mobile') {
|
|
697
|
+
await this.applyDeviceMode(session, session.page, 'mobile');
|
|
690
698
|
}
|
|
691
699
|
await session.page
|
|
692
700
|
.evaluate(({ x, y }) => window.scrollTo(x, y), scrollPosition)
|
|
@@ -711,8 +719,17 @@ export class TraceRecorder {
|
|
|
711
719
|
|
|
712
720
|
async applyDeviceMode(session, page, mode) {
|
|
713
721
|
if (mode === 'desktop') {
|
|
722
|
+
await page.setViewportSize?.({ ...DESKTOP_VIEWPORT });
|
|
714
723
|
if (typeof session.context.newCDPSession !== 'function') return;
|
|
715
724
|
}
|
|
725
|
+
if (mode === 'mobile') {
|
|
726
|
+
// CDP changes the mobile UA/touch model; setViewportSize also changes
|
|
727
|
+
// Playwright's screenshot dimensions from the desktop context size.
|
|
728
|
+
await page.setViewportSize?.({
|
|
729
|
+
width: IPHONE_14_PRO_MAX_DEVICE.width,
|
|
730
|
+
height: IPHONE_14_PRO_MAX_DEVICE.height
|
|
731
|
+
});
|
|
732
|
+
}
|
|
716
733
|
if (typeof session.context.newCDPSession !== 'function') {
|
|
717
734
|
throw new Error('当前浏览器不支持移动端 CDP 模拟');
|
|
718
735
|
}
|
|
@@ -763,7 +780,11 @@ export class TraceRecorder {
|
|
|
763
780
|
if (action === 'toggle-device') return this.toggleDeviceMode(session);
|
|
764
781
|
if (action === 'screenshot') {
|
|
765
782
|
if (session.state !== 'recording') return { state: session.state, captured: false };
|
|
766
|
-
return this.captureScreenshot(session).then(() => ({
|
|
783
|
+
return this.captureScreenshot(session).then(() => ({
|
|
784
|
+
state: session.state,
|
|
785
|
+
deviceMode: session.deviceMode,
|
|
786
|
+
captured: true
|
|
787
|
+
}));
|
|
767
788
|
}
|
|
768
789
|
if (action === 'start') {
|
|
769
790
|
if (session.state === 'recording') return { state: session.state };
|
|
@@ -790,6 +811,12 @@ export class TraceRecorder {
|
|
|
790
811
|
|
|
791
812
|
async captureScreenshot(session) {
|
|
792
813
|
if (!session.tracingStarted || session.state !== 'recording') return;
|
|
814
|
+
// Re-assert the live device mode immediately before capture. This is
|
|
815
|
+
// needed when the user switches devices after recording has started.
|
|
816
|
+
await this.applyDeviceMode(session, session.page, session.deviceMode);
|
|
817
|
+
session.metadata.viewport = session.deviceMode === 'mobile'
|
|
818
|
+
? { width: IPHONE_14_PRO_MAX_DEVICE.width, height: IPHONE_14_PRO_MAX_DEVICE.height }
|
|
819
|
+
: { ...DESKTOP_VIEWPORT };
|
|
793
820
|
session.stepIndex += 1;
|
|
794
821
|
let title = '未命名页面';
|
|
795
822
|
if (typeof session.page.title === 'function') {
|
|
@@ -21,8 +21,9 @@ function createFixture(options = {}) {
|
|
|
21
21
|
groupEnd: async () => calls.push(['trace-group-end'])
|
|
22
22
|
};
|
|
23
23
|
const page = {
|
|
24
|
-
goto: async url => calls.push(['goto', url]),
|
|
24
|
+
goto: async (url, options) => calls.push(['goto', url, options]),
|
|
25
25
|
reload: async options => calls.push(['reload', options]),
|
|
26
|
+
setViewportSize: async viewport => calls.push(['viewport', viewport]),
|
|
26
27
|
evaluate: async () => 'Fixture User Agent',
|
|
27
28
|
waitForTimeout: async duration => calls.push(['wait', duration]),
|
|
28
29
|
screenshot: async options => calls.push(['screenshot', options]),
|
|
@@ -42,7 +43,10 @@ function createFixture(options = {}) {
|
|
|
42
43
|
newPage: async () => page,
|
|
43
44
|
close: async () => calls.push(['context-close'])
|
|
44
45
|
};
|
|
45
|
-
browser.newContext = async
|
|
46
|
+
browser.newContext = async options => {
|
|
47
|
+
calls.push(['new-context', options]);
|
|
48
|
+
return context;
|
|
49
|
+
};
|
|
46
50
|
browser.version = () => 'Fixture Chromium 1';
|
|
47
51
|
browser.close = async () => calls.push(['browser-close']);
|
|
48
52
|
|
|
@@ -106,6 +110,9 @@ test('page controls start recording, record activity, upload and clean up one se
|
|
|
106
110
|
assert.equal(started.uuid, undefined);
|
|
107
111
|
assert.equal(started.metadata.operator, '上下文操作人');
|
|
108
112
|
assert.equal(started.metadata.browserVersion, 'Fixture Chromium 1');
|
|
113
|
+
assert.equal(calls.find(call => call[0] === 'goto')[1], 'https://app.example.test/login');
|
|
114
|
+
assert.equal(calls.find(call => call[0] === 'goto')[2].timeout, 3_600_000);
|
|
115
|
+
assert.deepEqual(calls.find(call => call[0] === 'new-context')[1].viewport, { width: 1400, height: 740 });
|
|
109
116
|
assert.equal(calls.some(call => call[0] === 'trace-start'), false);
|
|
110
117
|
assert.match(calls.find(call => call[0] === 'init-script')[1].content, /开始录制/);
|
|
111
118
|
assert.match(calls.find(call => call[0] === 'init-script')[1].content, /data-trace-recorder-annotation/);
|
|
@@ -118,7 +125,7 @@ test('page controls start recording, record activity, upload and clean up one se
|
|
|
118
125
|
await recorder.activeSession.activityPromise;
|
|
119
126
|
assert.equal(recorder.activeSession.state, 'recording');
|
|
120
127
|
const screenshotResult = await bindings.__traceRecorderControl({}, 'screenshot');
|
|
121
|
-
assert.deepEqual(screenshotResult, { state: 'recording', captured: true });
|
|
128
|
+
assert.deepEqual(screenshotResult, { state: 'recording', deviceMode: 'desktop', captured: true });
|
|
122
129
|
assert.ok(calls.some(call => call[0] === 'screenshot' && call[1].fullPage === false));
|
|
123
130
|
assert.ok(calls.some(call => call[0] === 'trace-group' && call[1] === '步骤1 | 未命名页面'));
|
|
124
131
|
assert.ok(calls.some(call => call[0] === 'reload' && call[1].waitUntil === 'domcontentloaded'));
|
|
@@ -219,7 +226,7 @@ test('page stop control immediately stops and uploads, then exposes the result t
|
|
|
219
226
|
assert.match(result.replayLink, /trace-viewer/);
|
|
220
227
|
});
|
|
221
228
|
|
|
222
|
-
test('
|
|
229
|
+
test('switches between PC and iPhone 14 Pro Max during one session', async () => {
|
|
223
230
|
const { bindings, calls, recorder } = createFixture();
|
|
224
231
|
await recorder.start({ url: 'https://app.example.test' });
|
|
225
232
|
|
|
@@ -230,6 +237,7 @@ test('toggles desktop and iPhone 14 Pro Max emulation in the same session', asyn
|
|
|
230
237
|
deviceName: 'iPhone 14 Pro Max'
|
|
231
238
|
});
|
|
232
239
|
assert.ok(calls.some(call => call[0] === 'cdp' && call[1] === 'Emulation.setDeviceMetricsOverride'));
|
|
240
|
+
assert.ok(calls.some(call => call[0] === 'viewport' && call[1].width === 430 && call[1].height === 932));
|
|
233
241
|
assert.deepEqual(recorder.activeSession.metadata.viewport, { width: 430, height: 932 });
|
|
234
242
|
assert.match(recorder.activeSession.metadata.userAgent, /iPhone/);
|
|
235
243
|
|
|
@@ -237,6 +245,7 @@ test('toggles desktop and iPhone 14 Pro Max emulation in the same session', asyn
|
|
|
237
245
|
assert.equal(desktop.deviceMode, 'desktop');
|
|
238
246
|
assert.ok(calls.some(call => call[0] === 'cdp' && call[1] === 'Emulation.clearDeviceMetricsOverride'));
|
|
239
247
|
assert.equal(recorder.activeSession.metadata.userAgent, 'Fixture User Agent');
|
|
248
|
+
assert.deepEqual(recorder.activeSession.metadata.viewport, { width: 1400, height: 740 });
|
|
240
249
|
|
|
241
250
|
await recorder.stop();
|
|
242
251
|
});
|
package/package.json
CHANGED
|
@@ -14,10 +14,40 @@ description: 通过 trace-mcp-recorder 启动或停止 Playwright Trace 人工
|
|
|
14
14
|
1. 从原话提取 `url`、`version`、`title`、`operator`。支持 `key=value` 和自然语言表达,不改写用户给出的值。
|
|
15
15
|
2. `url` 必填;缺少时只询问目标页面地址。其他字段缺失时不要阻断录制。
|
|
16
16
|
3. 调用 `start_trace_recording`,只传入用户明确提供的可选字段。服务端会优先使用 MCP 上下文中的操作人身份。
|
|
17
|
-
4. 告知用户浏览器已打开:默认是 PC 端(viewport 1400×740);左下角显示可拖拽的 2×2 半透明控制栏,第一行是“开始录制”“停止录制”,第二行是“切换移动端”“截图”,下方提供“画笔”“撤销”“清空”。画笔使用 SVG
|
|
17
|
+
4. 告知用户浏览器已打开:默认是 PC 端(viewport 1400×740);左下角显示可拖拽的 2×2 半透明控制栏,第一行是“开始录制”“停止录制”,第二行是“切换移动端”“截图”,下方提供“画笔”“撤销”“清空”。画笔使用 SVG 路径覆盖层,开启后可用鼠标或触摸标注页面;点击“退出画笔”会立即关闭并清空已有标注,避免后续页面操作继续带着旧标注,截图会带上标注。“切换移动端 / 切换 PC 端”可以在录制过程中来回切换:移动端为 iPhone 14 Pro Max(430×932,DPR 3),PC 端为 1400×740;切换后当前页面立即调整 viewport,截图按当前模式生成。再点击“开始录制”;点击开始后服务会先启动 Trace,临时禁用 Chromium 缓存并刷新当前 SPA 页面,等待 DOM、CSS、字体和图片稳定后恢复缓存,随后才进入正式录制计时。页面导航和录制开始后的刷新最长等待 1 小时。刷新期间不要操作页面;页面稳定后按需操作,只有点击“截图”才会保存当前渲染画面,普通点击、输入和滚动不会生成自定义 Action。截图时所有控制按钮会临时隐藏。同一 BrowserContext 会保留 cookies、localStorage 和 sessionStorage,新打开的页面会继承当前设备模式。连续 2 分钟没有页面活动时服务会自动停止并上传。人工截图在官方 Trace Viewer 中显示为 `步骤N | 页面标题`。
|
|
18
18
|
|
|
19
19
|
示例:`trace-mcp-recorder 启动Playwright trace 访问https://cpd.dev.autobestdevops.com,version=v2.1.0 title=bug3452 operator=张三`。这里 `title=bug3452` 是本次录制的标题/功能描述,页面由 `url=https://cpd.dev.autobestdevops.com` 确定。
|
|
20
20
|
|
|
21
|
+
## 使用示例
|
|
22
|
+
|
|
23
|
+
用户可以显式调用 Skill:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
$trace-recorder 启动 Playwright trace,访问 https://cpd.dev.autobestdevops.com,version=v2.1.0,title=首页功能02,operator=张三
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
也可以直接表达录制意图,Skill 会自动匹配:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
请打开 https://cpd.dev.autobestdevops.com 录制首页功能,版本 v2.1.0,操作人张三
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
浏览器打开后,指导用户按以下顺序操作:
|
|
36
|
+
|
|
37
|
+
1. 默认使用 PC `1400×740`;需要移动端时点击“切换移动端”。
|
|
38
|
+
2. 点击左下角“开始录制”,等待页面刷新和资源稳定完成。
|
|
39
|
+
3. 普通页面操作不会生成自定义步骤;需要记录页面状态时点击“画笔”标注,再点击“截图”。
|
|
40
|
+
4. 完成后点击“停止录制”,或发送 `$trace-recorder 停止录制`。
|
|
41
|
+
|
|
42
|
+
上传确认示例:
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
$trace-recorder 停止录制
|
|
46
|
+
$trace-recorder 上传录制内容
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
用户明确说“停止录制并上传”时,直接调用 `stop_trace_recording` 并传入 `confirm=upload`。用户说“不上传”时传入 `confirm=cancel`。返回结果以工具本次响应为准,成功时整理回放链接、版本、标题、操作人和浏览器版本。
|
|
50
|
+
|
|
21
51
|
## 停止录制
|
|
22
52
|
|
|
23
53
|
页面左下角“停止录制”会立即停止并自动上传。用户随后要求获取回放链接时,调用 `stop_trace_recording` 读取这次按钮停止的结果,并整理回放链接和元数据。
|