@autobest-ui/agent 1.0.17 → 1.0.19
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# trace-mcp-recorder
|
|
2
2
|
|
|
3
|
-
`trace-mcp-recorder` 是基于 `@modelcontextprotocol/sdk` 和 `StdioServerTransport` 的 Node.js MCP Server。它打开非无头 Chromium
|
|
3
|
+
`trace-mcp-recorder` 是基于 `@modelcontextprotocol/sdk` 和 `StdioServerTransport` 的 Node.js MCP Server。它打开非无头 Chromium,并在页面左下角注入半透明的“开始录制”“停止录制”和设备切换按钮供测试人员手动控制 Trace;默认是 PC 端,也可切换为 iPhone 14 Pro Max 移动端模拟。点击开始后先自动刷新一次页面,确保 DOM Snapshot 所引用的图片、CSS 和网络资源被 Trace 捕获;后续用户活动经过防抖后以 `步骤N | 页面标题 | 事件类型` 的 Trace Group 写入轻量 Playwright Action,空闲超过 2 分钟自动结束,然后上传原生 `trace.zip` 和浏览器元数据,并返回私有后端的 Trace Viewer 链接。同一进程同一时间只允许一个录制任务。
|
|
4
4
|
|
|
5
5
|
## 安装与启动
|
|
6
6
|
|
|
@@ -38,12 +38,12 @@ npm start
|
|
|
38
38
|
|
|
39
39
|
## MCP 工具
|
|
40
40
|
|
|
41
|
-
- `start_trace_recording`:参数为必填 `url` 和可选 `version`、`title`、`operator
|
|
41
|
+
- `start_trace_recording`:参数为必填 `url` 和可选 `version`、`title`、`operator`。该工具打开页面,默认 PC 端;点击左下角“切换移动端”可使用 iPhone 14 Pro Max(430×932、DPR 3、触摸、iPhone User-Agent)模拟,点击“开始录制”后才真正启动 Trace。`url` 独立确定被测页面;`title` 是录制标题或功能描述,也可以直接填写 `bug3452` 这类 Bug 编号。系统不额外传递独立的 Bug 字段。MCP 认证上下文中的 `operator`、`name` 或 `preferred_username` 优先于参数。
|
|
42
42
|
- `stop_trace_recording`:首次调用不传参数,只停止 trace 并询问是否上传;用户确认后传 `confirm=upload`,取消时传 `confirm=cancel`。上传成功后返回回放链接及版本、标题、操作人、浏览器版本。
|
|
43
43
|
|
|
44
44
|
录制超时后服务自动停止并自动上传(无需人工确认),随后第一次人工调用 `stop_trace_recording` 会取回自动停止的结果;再调用则返回没有活动会话。
|
|
45
45
|
|
|
46
|
-
Trace
|
|
46
|
+
Trace 在点击页面左下角“开始录制”后先刷新页面,再开始计时;点击“停止录制”会立即停止并自动上传,随后可通过 `stop_trace_recording` 取回回放链接。设备切换和录制控制按钮不会写入操作步骤;新打开的页面会继承当前设备模式。连续用户操作停止 800ms 后写入一个带自定义名称的 `tracing.group()`,组内使用 `waitForTimeout(1)` 推进官方 Viewer 播放;连续 2 分钟没有真实用户操作时自动停止并上传。停止录制时会补一个最终 Action。页面自身的轮播、动画和图片加载不会触发录制节点。官方 Viewer 的具体显示可能是 Group 标签或 Group 内嵌 Wait 节点,取决于 Viewer 版本。
|
|
47
47
|
|
|
48
48
|
## 后端接口契约
|
|
49
49
|
|
|
@@ -51,7 +51,7 @@ Trace 在点击页面左下角“开始录制”后才开始计时;点击“
|
|
|
51
51
|
|
|
52
52
|
- `POST /api/trace-sessions`:接收 `multipart/form-data`,必填字段为 `trace`、`target_url`,可选字段为 `version`、`title`、`operator`、`browser_name`、`browser_version`、`user_agent`、`os_name`、`viewport`;后端生成 UUID,创建并上传成功后返回 `{ "uuid": "...", "fileUrl": "...", "item": { "status": "completed" } }`。
|
|
53
53
|
|
|
54
|
-
`metadata`
|
|
54
|
+
`metadata` 包含浏览器版本、当前 User-Agent、viewport、设备模式(PC 或 iPhone 14 Pro Max)、MCP 所在操作系统信息、目标 URL 和录制业务字段。上传或状态更新失败时,本地 trace 仍会被清理,会话会尽力回写为 `failed`。
|
|
55
55
|
|
|
56
56
|
## 验证
|
|
57
57
|
|
|
@@ -15,6 +15,15 @@ import { z } from 'zod';
|
|
|
15
15
|
const DEFAULT_MAX_RECORD_DURATION = 1_800_000;
|
|
16
16
|
const USER_ACTIVITY_DEBOUNCE_DELAY = 800;
|
|
17
17
|
const IDLE_STOP_DELAY = 120_000;
|
|
18
|
+
const IPHONE_14_PRO_MAX_DEVICE = {
|
|
19
|
+
name: 'iPhone 14 Pro Max',
|
|
20
|
+
width: 430,
|
|
21
|
+
height: 932,
|
|
22
|
+
deviceScaleFactor: 3,
|
|
23
|
+
userAgent:
|
|
24
|
+
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 ' +
|
|
25
|
+
'(KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1'
|
|
26
|
+
};
|
|
18
27
|
const directory = path.dirname(fileURLToPath(import.meta.url));
|
|
19
28
|
|
|
20
29
|
function readConfiguration(environment = process.env) {
|
|
@@ -162,32 +171,37 @@ function createRecorderPageScript() {
|
|
|
162
171
|
button:disabled { cursor: default; opacity: .42; }
|
|
163
172
|
.start { background: rgba(22, 163, 74, .82); }
|
|
164
173
|
.stop { background: rgba(220, 38, 38, .82); }
|
|
174
|
+
.device { background: rgba(37, 99, 235, .82); min-width: 102px; }
|
|
165
175
|
</style>
|
|
166
176
|
<div class="toolbar" role="group" aria-label="Trace 录制控制">
|
|
167
177
|
<button class="start" type="button" data-action="start" data-trace-recorder-control>开始录制</button>
|
|
168
178
|
<button class="stop" type="button" data-action="stop" data-trace-recorder-control>停止录制</button>
|
|
179
|
+
<button class="device" type="button" data-action="toggle-device" data-trace-recorder-control>切换移动端</button>
|
|
169
180
|
</div>
|
|
170
181
|
\`;
|
|
171
182
|
document.documentElement.append(host);
|
|
172
183
|
const startButton = shadow.querySelector('[data-action="start"]');
|
|
173
184
|
const stopButton = shadow.querySelector('[data-action="stop"]');
|
|
185
|
+
const deviceButton = shadow.querySelector('[data-action="toggle-device"]');
|
|
174
186
|
|
|
175
|
-
const render = state => {
|
|
187
|
+
const render = (state, deviceMode = 'desktop') => {
|
|
176
188
|
const recording = state === 'recording';
|
|
177
189
|
const waiting = state === 'waiting_for_start';
|
|
178
190
|
startButton.disabled = !waiting;
|
|
179
191
|
stopButton.disabled = !recording && !waiting;
|
|
192
|
+
deviceButton.disabled = state === 'starting' || state === 'stopping' || state === 'uploading';
|
|
180
193
|
startButton.textContent = recording ? '录制中' : '开始录制';
|
|
181
194
|
stopButton.textContent = state === 'stopping' || state === 'uploading' ? '正在停止' : '停止录制';
|
|
195
|
+
deviceButton.textContent = deviceMode === 'mobile' ? '切换 PC 端' : '切换移动端';
|
|
182
196
|
};
|
|
183
197
|
const control = async action => {
|
|
184
198
|
startButton.disabled = true;
|
|
185
199
|
stopButton.disabled = true;
|
|
186
200
|
try {
|
|
187
201
|
const result = await globalThis.__traceRecorderControl?.(action);
|
|
188
|
-
render(result?.state);
|
|
202
|
+
render(result?.state, result?.deviceMode);
|
|
189
203
|
} catch {
|
|
190
|
-
render('waiting_for_start');
|
|
204
|
+
render('waiting_for_start', 'desktop');
|
|
191
205
|
}
|
|
192
206
|
};
|
|
193
207
|
startButton.addEventListener('click', event => {
|
|
@@ -201,10 +215,15 @@ function createRecorderPageScript() {
|
|
|
201
215
|
render('stopping');
|
|
202
216
|
void control('stop');
|
|
203
217
|
});
|
|
218
|
+
deviceButton.addEventListener('click', event => {
|
|
219
|
+
event.preventDefault();
|
|
220
|
+
event.stopPropagation();
|
|
221
|
+
void control('toggle-device');
|
|
222
|
+
});
|
|
204
223
|
|
|
205
224
|
const syncState = () => {
|
|
206
225
|
void Promise.resolve(globalThis.__traceRecorderControl?.('status'))
|
|
207
|
-
.then(result => render(result?.state))
|
|
226
|
+
.then(result => render(result?.state, result?.deviceMode))
|
|
208
227
|
.catch(() => {});
|
|
209
228
|
};
|
|
210
229
|
syncState();
|
|
@@ -307,6 +326,8 @@ export class TraceRecorder {
|
|
|
307
326
|
browserVersion: browser.version(),
|
|
308
327
|
userAgent: await page.evaluate(() => navigator.userAgent),
|
|
309
328
|
viewport: page.viewportSize(),
|
|
329
|
+
deviceMode: 'desktop',
|
|
330
|
+
deviceName: 'Desktop',
|
|
310
331
|
operatingSystem: this.platformInfo(),
|
|
311
332
|
startedAt: new Date().toISOString()
|
|
312
333
|
};
|
|
@@ -330,10 +351,24 @@ export class TraceRecorder {
|
|
|
330
351
|
pendingActivity: undefined,
|
|
331
352
|
stepIndex: 0,
|
|
332
353
|
tracingStarted: false,
|
|
333
|
-
lastActivityAt: undefined
|
|
354
|
+
lastActivityAt: undefined,
|
|
355
|
+
deviceMode: 'desktop',
|
|
356
|
+
deviceName: 'Desktop',
|
|
357
|
+
desktopUserAgent: metadata.userAgent,
|
|
358
|
+
cdpSessions: new Map()
|
|
334
359
|
};
|
|
335
360
|
this.activeSession = session;
|
|
336
361
|
|
|
362
|
+
if (typeof context.on === 'function') {
|
|
363
|
+
context.on('page', openedPage => {
|
|
364
|
+
if (session.deviceMode === 'mobile') {
|
|
365
|
+
void this.applyDeviceMode(session, openedPage, 'mobile').catch(error => {
|
|
366
|
+
console.error('[trace-mcp-recorder] 新页面应用移动端模拟失败:', errorMessage(error));
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
337
372
|
const failOnBrowserExit = () => {
|
|
338
373
|
if (session.state === 'recording' || session.state === 'waiting_for_start') {
|
|
339
374
|
session.stopPromise = this.failSession(session, new Error('Chromium 浏览器意外退出'));
|
|
@@ -456,6 +491,11 @@ export class TraceRecorder {
|
|
|
456
491
|
if (session.tracingStarted || session.state !== 'waiting_for_start') return;
|
|
457
492
|
await session.context.tracing.start({ screenshots: true, snapshots: true, sources: true });
|
|
458
493
|
session.tracingStarted = true;
|
|
494
|
+
// The initial navigation happened before Trace started. Reload now so the
|
|
495
|
+
// DOM snapshot also has response bodies for images, CSS, and other assets.
|
|
496
|
+
if (typeof session.page.reload === 'function') {
|
|
497
|
+
await session.page.reload({ waitUntil: 'load' });
|
|
498
|
+
}
|
|
459
499
|
session.state = 'recording';
|
|
460
500
|
session.lastActivityAt = Date.now();
|
|
461
501
|
session.timer = this.setTimer(() => {
|
|
@@ -468,18 +508,65 @@ export class TraceRecorder {
|
|
|
468
508
|
this.armIdleTimer(session);
|
|
469
509
|
}
|
|
470
510
|
|
|
511
|
+
async applyDeviceMode(session, page, mode) {
|
|
512
|
+
if (mode === 'desktop') {
|
|
513
|
+
if (typeof session.context.newCDPSession !== 'function') return;
|
|
514
|
+
}
|
|
515
|
+
if (typeof session.context.newCDPSession !== 'function') {
|
|
516
|
+
throw new Error('当前浏览器不支持移动端 CDP 模拟');
|
|
517
|
+
}
|
|
518
|
+
let cdp = session.cdpSessions.get(page);
|
|
519
|
+
if (!cdp) {
|
|
520
|
+
cdp = await session.context.newCDPSession(page);
|
|
521
|
+
session.cdpSessions.set(page, cdp);
|
|
522
|
+
}
|
|
523
|
+
if (mode === 'mobile') {
|
|
524
|
+
await cdp.send('Emulation.setDeviceMetricsOverride', {
|
|
525
|
+
width: IPHONE_14_PRO_MAX_DEVICE.width,
|
|
526
|
+
height: IPHONE_14_PRO_MAX_DEVICE.height,
|
|
527
|
+
deviceScaleFactor: IPHONE_14_PRO_MAX_DEVICE.deviceScaleFactor,
|
|
528
|
+
mobile: true,
|
|
529
|
+
screenWidth: IPHONE_14_PRO_MAX_DEVICE.width,
|
|
530
|
+
screenHeight: IPHONE_14_PRO_MAX_DEVICE.height
|
|
531
|
+
});
|
|
532
|
+
await cdp.send('Emulation.setTouchEmulationEnabled', { enabled: true, maxTouchPoints: 5 });
|
|
533
|
+
await cdp.send('Network.setUserAgentOverride', { userAgent: IPHONE_14_PRO_MAX_DEVICE.userAgent });
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
await cdp.send('Emulation.clearDeviceMetricsOverride');
|
|
537
|
+
await cdp.send('Emulation.setTouchEmulationEnabled', { enabled: false });
|
|
538
|
+
await cdp.send('Network.setUserAgentOverride', { userAgent: session.desktopUserAgent });
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async toggleDeviceMode(session) {
|
|
542
|
+
if (session.state !== 'waiting_for_start' && session.state !== 'recording') {
|
|
543
|
+
return { state: session.state, deviceMode: session.deviceMode };
|
|
544
|
+
}
|
|
545
|
+
const nextMode = session.deviceMode === 'mobile' ? 'desktop' : 'mobile';
|
|
546
|
+
await this.applyDeviceMode(session, session.page, nextMode);
|
|
547
|
+
session.deviceMode = nextMode;
|
|
548
|
+
session.deviceName = nextMode === 'mobile' ? IPHONE_14_PRO_MAX_DEVICE.name : 'Desktop';
|
|
549
|
+
session.metadata.deviceMode = session.deviceMode;
|
|
550
|
+
session.metadata.deviceName = session.deviceName;
|
|
551
|
+
session.metadata.userAgent = nextMode === 'mobile'
|
|
552
|
+
? IPHONE_14_PRO_MAX_DEVICE.userAgent
|
|
553
|
+
: session.desktopUserAgent;
|
|
554
|
+
session.metadata.viewport = nextMode === 'mobile'
|
|
555
|
+
? { width: IPHONE_14_PRO_MAX_DEVICE.width, height: IPHONE_14_PRO_MAX_DEVICE.height }
|
|
556
|
+
: session.page.viewportSize();
|
|
557
|
+
return { state: session.state, deviceMode: session.deviceMode, deviceName: session.deviceName };
|
|
558
|
+
}
|
|
559
|
+
|
|
471
560
|
handlePageControl(session, action) {
|
|
472
|
-
if (action === 'status') return { state: session.state };
|
|
561
|
+
if (action === 'status') return { state: session.state, deviceMode: session.deviceMode };
|
|
562
|
+
if (action === 'toggle-device') return this.toggleDeviceMode(session);
|
|
473
563
|
if (action === 'start') {
|
|
474
564
|
if (session.state === 'recording') return { state: session.state };
|
|
475
565
|
if (session.state !== 'waiting_for_start') return { state: session.state };
|
|
476
566
|
if (!session.activityPromise) {
|
|
477
|
-
session.activityPromise = this.beginRecording(session).catch(error =>
|
|
478
|
-
session.state = 'failed';
|
|
479
|
-
throw error;
|
|
480
|
-
});
|
|
567
|
+
session.activityPromise = this.beginRecording(session).catch(error => this.failSession(session, error));
|
|
481
568
|
}
|
|
482
|
-
return session.activityPromise.then(() => ({ state: session.state }));
|
|
569
|
+
return session.activityPromise.then(() => ({ state: session.state, deviceMode: session.deviceMode }));
|
|
483
570
|
}
|
|
484
571
|
if (action === 'stop') {
|
|
485
572
|
if (session.state !== 'recording' && session.state !== 'waiting_for_start') {
|
|
@@ -571,7 +658,9 @@ export class TraceRecorder {
|
|
|
571
658
|
['browser_version', session.metadata.browserVersion],
|
|
572
659
|
['user_agent', session.metadata.userAgent],
|
|
573
660
|
['os_name', session.metadata.operatingSystem.platform],
|
|
574
|
-
['viewport', JSON.stringify(session.metadata.viewport)]
|
|
661
|
+
['viewport', JSON.stringify(session.metadata.viewport)],
|
|
662
|
+
['device_mode', session.metadata.deviceMode],
|
|
663
|
+
['device_name', session.metadata.deviceName]
|
|
575
664
|
]) {
|
|
576
665
|
if (value !== undefined && value !== null) form.append(field, String(value));
|
|
577
666
|
}
|
|
@@ -595,7 +684,10 @@ export class TraceRecorder {
|
|
|
595
684
|
version: session.metadata.version,
|
|
596
685
|
title: session.metadata.title,
|
|
597
686
|
operator: session.metadata.operator,
|
|
598
|
-
browserVersion: session.metadata.browserVersion
|
|
687
|
+
browserVersion: session.metadata.browserVersion,
|
|
688
|
+
deviceMode: session.metadata.deviceMode,
|
|
689
|
+
deviceName: session.metadata.deviceName,
|
|
690
|
+
viewport: session.metadata.viewport
|
|
599
691
|
}
|
|
600
692
|
};
|
|
601
693
|
session.state = 'completed';
|
|
@@ -629,6 +721,7 @@ export class TraceRecorder {
|
|
|
629
721
|
this.clearTimer(session.timer);
|
|
630
722
|
this.clearTimer(session.idleTimer);
|
|
631
723
|
this.clearTimer(session.activityDebounceTimer);
|
|
724
|
+
if (session.tracingStarted) await session.context.tracing.stop({ path: session.tracePath }).catch(() => {});
|
|
632
725
|
await this.closeResources(session);
|
|
633
726
|
await this.removeTemporaryDirectory(session.temporaryDirectory).catch(() => {});
|
|
634
727
|
if (this.activeSession === session) this.activeSession = undefined;
|
|
@@ -22,6 +22,7 @@ function createFixture(options = {}) {
|
|
|
22
22
|
};
|
|
23
23
|
const page = {
|
|
24
24
|
goto: async url => calls.push(['goto', url]),
|
|
25
|
+
reload: async options => calls.push(['reload', options]),
|
|
25
26
|
evaluate: async () => 'Fixture User Agent',
|
|
26
27
|
waitForTimeout: async duration => calls.push(['wait', duration]),
|
|
27
28
|
viewportSize: () => ({ width: 1280, height: 720 }),
|
|
@@ -29,6 +30,9 @@ function createFixture(options = {}) {
|
|
|
29
30
|
};
|
|
30
31
|
const context = {
|
|
31
32
|
tracing,
|
|
33
|
+
newCDPSession: async page => ({
|
|
34
|
+
send: async (method, params) => calls.push(['cdp', method, params])
|
|
35
|
+
}),
|
|
32
36
|
exposeBinding: async (name, callback) => {
|
|
33
37
|
calls.push(['expose-binding', name]);
|
|
34
38
|
bindings[name] = callback;
|
|
@@ -103,7 +107,11 @@ test('page controls start recording, record activity, upload and clean up one se
|
|
|
103
107
|
assert.equal(started.metadata.browserVersion, 'Fixture Chromium 1');
|
|
104
108
|
assert.equal(calls.some(call => call[0] === 'trace-start'), false);
|
|
105
109
|
assert.match(calls.find(call => call[0] === 'init-script')[1].content, /开始录制/);
|
|
106
|
-
assert.deepEqual(await bindings.__traceRecorderControl({}, 'start'), {
|
|
110
|
+
assert.deepEqual(await bindings.__traceRecorderControl({}, 'start'), {
|
|
111
|
+
state: 'recording',
|
|
112
|
+
deviceMode: 'desktop'
|
|
113
|
+
});
|
|
114
|
+
assert.ok(calls.some(call => call[0] === 'reload' && call[1].waitUntil === 'load'));
|
|
107
115
|
recorder.handleUserActivity(recorder.activeSession, {
|
|
108
116
|
type: 'click',
|
|
109
117
|
title: '首页功能'
|
|
@@ -192,6 +200,28 @@ test('page stop control immediately stops and uploads, then exposes the result t
|
|
|
192
200
|
assert.match(result.replayLink, /trace-viewer/);
|
|
193
201
|
});
|
|
194
202
|
|
|
203
|
+
test('toggles desktop and iPhone 14 Pro Max emulation in the same session', async () => {
|
|
204
|
+
const { bindings, calls, recorder } = createFixture();
|
|
205
|
+
await recorder.start({ url: 'https://app.example.test' });
|
|
206
|
+
|
|
207
|
+
const mobile = await bindings.__traceRecorderControl({}, 'toggle-device');
|
|
208
|
+
assert.deepEqual(mobile, {
|
|
209
|
+
state: 'waiting_for_start',
|
|
210
|
+
deviceMode: 'mobile',
|
|
211
|
+
deviceName: 'iPhone 14 Pro Max'
|
|
212
|
+
});
|
|
213
|
+
assert.ok(calls.some(call => call[0] === 'cdp' && call[1] === 'Emulation.setDeviceMetricsOverride'));
|
|
214
|
+
assert.deepEqual(recorder.activeSession.metadata.viewport, { width: 430, height: 932 });
|
|
215
|
+
assert.match(recorder.activeSession.metadata.userAgent, /iPhone/);
|
|
216
|
+
|
|
217
|
+
const desktop = await bindings.__traceRecorderControl({}, 'toggle-device');
|
|
218
|
+
assert.equal(desktop.deviceMode, 'desktop');
|
|
219
|
+
assert.ok(calls.some(call => call[0] === 'cdp' && call[1] === 'Emulation.clearDeviceMetricsOverride'));
|
|
220
|
+
assert.equal(recorder.activeSession.metadata.userAgent, 'Fixture User Agent');
|
|
221
|
+
|
|
222
|
+
await recorder.stop();
|
|
223
|
+
});
|
|
224
|
+
|
|
195
225
|
test('exposes both tools and the bundled skill over stdio', async () => {
|
|
196
226
|
const client = new Client({ name: 'trace-mcp-recorder-test', version: '1.0.0' });
|
|
197
227
|
const transport = new StdioClientTransport({
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
12
12
|
"axios": "1.20.0",
|
|
13
|
+
"fflate": "0.8.2",
|
|
13
14
|
"form-data": "4.0.6",
|
|
15
|
+
"jpeg-js": "0.4.4",
|
|
14
16
|
"playwright": "1.63.0",
|
|
15
17
|
"uuid": "14.0.2",
|
|
16
18
|
"zod": "4.4.3"
|
|
@@ -496,6 +498,11 @@
|
|
|
496
498
|
}
|
|
497
499
|
]
|
|
498
500
|
},
|
|
501
|
+
"node_modules/fflate": {
|
|
502
|
+
"version": "0.8.2",
|
|
503
|
+
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
|
504
|
+
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
|
|
505
|
+
},
|
|
499
506
|
"node_modules/finalhandler": {
|
|
500
507
|
"version": "2.1.1",
|
|
501
508
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
|
|
@@ -768,6 +775,11 @@
|
|
|
768
775
|
"url": "https://github.com/sponsors/panva"
|
|
769
776
|
}
|
|
770
777
|
},
|
|
778
|
+
"node_modules/jpeg-js": {
|
|
779
|
+
"version": "0.4.4",
|
|
780
|
+
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
|
|
781
|
+
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="
|
|
782
|
+
},
|
|
771
783
|
"node_modules/json-schema-traverse": {
|
|
772
784
|
"version": "1.0.0",
|
|
773
785
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
|
@@ -1613,6 +1625,11 @@
|
|
|
1613
1625
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
|
|
1614
1626
|
"integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg=="
|
|
1615
1627
|
},
|
|
1628
|
+
"fflate": {
|
|
1629
|
+
"version": "0.8.2",
|
|
1630
|
+
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
|
1631
|
+
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="
|
|
1632
|
+
},
|
|
1616
1633
|
"finalhandler": {
|
|
1617
1634
|
"version": "2.1.1",
|
|
1618
1635
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
|
|
@@ -1789,6 +1806,11 @@
|
|
|
1789
1806
|
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz",
|
|
1790
1807
|
"integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw=="
|
|
1791
1808
|
},
|
|
1809
|
+
"jpeg-js": {
|
|
1810
|
+
"version": "0.4.4",
|
|
1811
|
+
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
|
|
1812
|
+
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="
|
|
1813
|
+
},
|
|
1792
1814
|
"json-schema-traverse": {
|
|
1793
1815
|
"version": "1.0.0",
|
|
1794
1816
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@ 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.
|
|
17
|
+
4. 告知用户浏览器已打开:默认是 PC 端;如需移动端测试,先点击左下角“切换移动端”(iPhone 14 Pro Max,430×932,DPR 3),再点击“开始录制”;点击开始后服务会先刷新一次当前页面以捕获图片等资源,再启动计时,用户正式操作前不要提交表单或进行其他操作;操作完成后点击“停止录制”。新打开的页面会继承当前设备模式。连续 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
|
|