@taole/dd-playwright 0.1.10 → 0.1.12
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/README.md +3 -1
- package/SKILL.md +22 -1
- package/bin/mcp-server.mjs +46 -4
- package/package.json +4 -1
- package/src/browser-manager.mjs +84 -11
- package/src/config.mjs +2 -0
- package/src/server/web-server.mjs +20 -1
package/README.md
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
| 工具 | 说明 |
|
|
10
10
|
|---|---|
|
|
11
|
-
| `browser_navigate` | 访问 URL。支持显式指定 `uid` 选择绑定账号(默认使用当前默认账号);支持通过 `anonymous: true`
|
|
11
|
+
| `browser_navigate` | 访问 URL。支持显式指定 `uid` 选择绑定账号(默认使用当前默认账号);支持通过 `anonymous: true` 模拟未登录游客状态访问;支持 `device`(如 `iPhone 13`)启用完整移动端仿真(视口+UA+触摸),传 `desktop` 恢复桌面模式;支持 `userAgent` 会话级自定义 UA,传 `default` 清除 |
|
|
12
|
+
| `browser_resize` | 调整当前页面渲染视口(对标 playwright-mcp)。只改 innerWidth/innerHeight,不改 UA/触摸;完整移动仿真请用 `browser_navigate` 的 `device` 参数 |
|
|
12
13
|
| `browser_screenshot` | 页面/元素截图,落盘到数据目录并返回路径 |
|
|
13
14
|
| `browser_get_console_errors` | 收集 console.error 与未捕获异常 |
|
|
14
15
|
| `browser_click_element` | 按选择器模拟用户点击元素 |
|
|
@@ -42,6 +43,7 @@ npx playwright install chromium
|
|
|
42
43
|
- **数据目录**:默认自动固定为 `~/.dd-browser-mcp`(账号库 `runtime.db` 与截图目录),无需手动设置环境变量。
|
|
43
44
|
- **无头模式 (Headless)**:默认为 `true`(静默后台运行),可在 Web 控制台(`http://127.0.0.1:12309`)的「全局运行配置」中一键开关切换为可见窗口。
|
|
44
45
|
- **域名白名单**:默认为 `tuwan.com`,可通过 `BROWSER_MCP_DOMAIN_SUFFIX` 按需调整。
|
|
46
|
+
- **自定义 UA**:优先用 `browser_navigate` 的 `userAgent` 参数(会话级,随调随改);也可用环境变量 `BROWSER_MCP_USER_AGENT` 设置启动级默认值。UA 优先级:工具参数 > 环境变量 > `device` 设备描述符 > 浏览器默认;自定义 UA 只替换 UA 字符串,视口与触摸仿真仍按 `device` 生效。
|
|
45
47
|
|
|
46
48
|
登录态来源优先级:`BROWSER_MCP_STORAGE_STATE` 文件 > `~/.dd-browser-mcp/runtime.db` 中激活的账号 > 无登录态。
|
|
47
49
|
|
package/SKILL.md
CHANGED
|
@@ -31,7 +31,28 @@ description: >-
|
|
|
31
31
|
4. **请求头注入**:`y.tuwan.com reqHeaders://{debug: '1'}`
|
|
32
32
|
> 更多高级协议与规则参考官方文档:https://wproxy.org/whistle/rules/
|
|
33
33
|
|
|
34
|
-
## 3.
|
|
34
|
+
## 3. 设备与视口仿真(移动端/H5 验收)
|
|
35
|
+
- **移动端验收**:调用 `browser_navigate` 时传 `device`(Playwright 设备描述符名,如 `"iPhone 13"`、`"Pixel 7"`),一次性启用完整移动仿真:移动视口 + 移动 UA + isMobile + 触摸事件(hasTouch)。设置后整个会话保持,后续导航无需重复传;传 `"desktop"` 恢复默认 1280×800 桌面模式。
|
|
36
|
+
- **切换时机**:切换 device 会重建浏览器上下文(当前页面关闭后重开目标 URL,登录态自动重挂),必须在导航时一并指定,严禁在已打开页面的会话中途切换。
|
|
37
|
+
- **仅调视口**:只需改变 innerWidth/innerHeight(如响应式断点验证)时调用 `browser_resize({ width, height })`;它不改 UA 与触摸仿真,页面内执行 `window.resizeTo` 对 Playwright 视口无效,不要用它尝试改视口。
|
|
38
|
+
- **常见设备名参考**:`iPhone 13`(390×844)、`iPhone 15 Pro`(393×852)、`Pixel 7`(412×915)、`iPad Mini`(768×1024);传错名称时工具会返回错误提示与示例。
|
|
39
|
+
- **自定义 UA**:调用 `browser_navigate` 时传 `userAgent`(如模拟 App 内 WebView:`"WebView/9.9 ..."`),会话级生效、整个会话保持;只替换 UA 字符串,不影响视口与触摸仿真,可与 `device` 同时指定。传 `"default"` 清除会话级设置。UA 优先级:工具参数 > 环境变量 `BROWSER_MCP_USER_AGENT`(启动级默认值)> `device` 描述符 > 浏览器默认。与 `device` 一样,切换 UA 会重建上下文,必须在导航时一并指定;`browser_status.sessionUserAgent` / `envUserAgent` 可确认当前生效值。
|
|
40
|
+
- **典型示例**:
|
|
41
|
+
```
|
|
42
|
+
// 完整移动端仿真(视口 390x844 + iPhone UA + 触摸),会话级保持
|
|
43
|
+
browser_navigate({ url: "https://xdd-test.tuwan.com/", device: "iPhone 13" })
|
|
44
|
+
|
|
45
|
+
// 模拟 App 内 WebView:设备仿真 + 自定义 UA(只换 UA,视口/触摸仍按 iPhone 13)
|
|
46
|
+
browser_navigate({ url: "https://xdd-test.tuwan.com/", device: "iPhone 13", userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) TuwanApp/8.0.0" })
|
|
47
|
+
|
|
48
|
+
// 只调视口验证响应式断点(不改 UA / 触摸)
|
|
49
|
+
browser_resize({ width: 375, height: 812 })
|
|
50
|
+
|
|
51
|
+
// 恢复桌面模式并清除自定义 UA
|
|
52
|
+
browser_navigate({ url: "https://xdd-test.tuwan.com/", device: "desktop", userAgent: "default" })
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 4. 页面交互规范
|
|
35
56
|
- **元素定位**:在动态页面或选择器不确定时,优先调用 `browser_get_dom` 提取当前可见的交互元素结构(包含按钮、输入框、链接及其选择器),严禁盲猜选择器。
|
|
36
57
|
- **表单与按键**:表单输入调用 `browser_fill_text`(原生触发 `input` 与 `change` 事件);键盘按键/回车提交调用 `browser_press_key`。
|
|
37
58
|
- **状态排查**:排查免登异常前先调用 `browser_status` 确认 MCP 服务凭据挂载状态与当前 UID。
|
package/bin/mcp-server.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { resolveConfig } from "../src/config.mjs";
|
|
|
11
11
|
import { PlaywrightBrowserManager } from "../src/browser-manager.mjs";
|
|
12
12
|
import { pathToFileURL } from "node:url";
|
|
13
13
|
|
|
14
|
-
export const MCP_VERSION = "0.1.
|
|
14
|
+
export const MCP_VERSION = "0.1.12";
|
|
15
15
|
|
|
16
16
|
function textResult(payload) {
|
|
17
17
|
return {
|
|
@@ -38,7 +38,8 @@ export function createBrowserMcpServer({ manager, config }) {
|
|
|
38
38
|
description:
|
|
39
39
|
"使用内置 Playwright 访问目标 URL。" +
|
|
40
40
|
"可显式指定 uid 选择绑定的点点账号(不传则使用默认激活账号);" +
|
|
41
|
-
"若传 anonymous: true
|
|
41
|
+
"若传 anonymous: true 则以完全未登录的游客身份访问;" +
|
|
42
|
+
"移动端/H5 验收可传 device 启用完整设备仿真(视口+UA+触摸),也可单独传 userAgent 自定义 UA。" +
|
|
42
43
|
"返回 HTTP 状态码、页面标题与控制台报错统计。",
|
|
43
44
|
inputSchema: {
|
|
44
45
|
url: z.string().describe("需要访问的目标页面 URL"),
|
|
@@ -50,6 +51,23 @@ export function createBrowserMcpServer({ manager, config }) {
|
|
|
50
51
|
.boolean()
|
|
51
52
|
.optional()
|
|
52
53
|
.describe("是否以匿名/未登录状态访问(默认 false)。若为 true,则不注入任何 Cookie/storageState"),
|
|
54
|
+
device: z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe(
|
|
58
|
+
"可选 Playwright 设备描述符名称(如 'iPhone 13'、'iPhone 15 Pro'、'Pixel 7'),启用完整移动端仿真:" +
|
|
59
|
+
"视口 + 移动 UA + isMobile + hasTouch。设置后整个会话保持该设备,后续导航无需重复传。" +
|
|
60
|
+
"传 'desktop' 恢复默认 1280x800 桌面模式。" +
|
|
61
|
+
"注意:切换设备会重建浏览器上下文(当前页面关闭后重开目标 URL,登录态自动重挂),请在导航时一并指定,不要在中途切换。",
|
|
62
|
+
),
|
|
63
|
+
userAgent: z
|
|
64
|
+
.string()
|
|
65
|
+
.optional()
|
|
66
|
+
.describe(
|
|
67
|
+
"可选自定义 User-Agent(会话级,设置后整个会话保持,后续导航无需重复传)。" +
|
|
68
|
+
"优先级高于设备描述符自带 UA 与环境变量 BROWSER_MCP_USER_AGENT;只替换 UA 字符串,不影响视口与触摸仿真。" +
|
|
69
|
+
"传 'default' 清除会话级设置。注意:切换 UA 会重建浏览器上下文(当前页面关闭后重开目标 URL,登录态自动重挂),请在导航时一并指定。",
|
|
70
|
+
),
|
|
53
71
|
waitFor: z
|
|
54
72
|
.string()
|
|
55
73
|
.optional()
|
|
@@ -60,10 +78,10 @@ export function createBrowserMcpServer({ manager, config }) {
|
|
|
60
78
|
.describe("导航与等待超时时间(毫秒),默认 15000"),
|
|
61
79
|
},
|
|
62
80
|
},
|
|
63
|
-
async ({ url, uid, anonymous, waitFor, timeout }) => {
|
|
81
|
+
async ({ url, uid, anonymous, device, userAgent, waitFor, timeout }) => {
|
|
64
82
|
try {
|
|
65
83
|
return textResult(
|
|
66
|
-
await manager.navigate({ url, uid, anonymous, waitFor, timeout }),
|
|
84
|
+
await manager.navigate({ url, uid, anonymous, device, userAgent, waitFor, timeout }),
|
|
67
85
|
);
|
|
68
86
|
} catch (err) {
|
|
69
87
|
return errorResult("导航失败", err);
|
|
@@ -268,6 +286,27 @@ export function createBrowserMcpServer({ manager, config }) {
|
|
|
268
286
|
},
|
|
269
287
|
);
|
|
270
288
|
|
|
289
|
+
server.registerTool(
|
|
290
|
+
"browser_resize",
|
|
291
|
+
{
|
|
292
|
+
description:
|
|
293
|
+
"调整当前页面的渲染视口尺寸(对标 playwright-mcp 的 browser_resize)。" +
|
|
294
|
+
"只影响 innerWidth/innerHeight 与布局视口,不修改 UA 与触摸仿真;" +
|
|
295
|
+
"若页面按 UA 判断端别或依赖触摸事件,请改用 browser_navigate 的 device 参数做完整移动端仿真。",
|
|
296
|
+
inputSchema: {
|
|
297
|
+
width: z.number().int().positive().describe("视口宽度(像素)"),
|
|
298
|
+
height: z.number().int().positive().describe("视口高度(像素)"),
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
async ({ width, height }) => {
|
|
302
|
+
try {
|
|
303
|
+
return textResult(await manager.resize({ width, height }));
|
|
304
|
+
} catch (err) {
|
|
305
|
+
return errorResult("调整视口失败", err);
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
);
|
|
309
|
+
|
|
271
310
|
server.registerTool(
|
|
272
311
|
"browser_get_dom",
|
|
273
312
|
{
|
|
@@ -399,6 +438,9 @@ export function createBrowserMcpServer({ manager, config }) {
|
|
|
399
438
|
storageStateLoaded: manager.hasAuth,
|
|
400
439
|
currentUid: manager.currentUid,
|
|
401
440
|
isAnonymous: manager.isAnonymous,
|
|
441
|
+
device: manager.device,
|
|
442
|
+
sessionUserAgent: manager.sessionUserAgent,
|
|
443
|
+
envUserAgent: manager.userAgent,
|
|
402
444
|
headless: manager.headless,
|
|
403
445
|
proxyStarted: manager.whistle.started,
|
|
404
446
|
proxyRule: manager.proxyRule,
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taole/dd-playwright",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Standalone stdio MCP & CLI: Playwright browser automation + isolated whistle proxy + diandian account storageState injection",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "ISC",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node --test test/*.test.mjs"
|
|
9
|
+
},
|
|
7
10
|
"bin": {
|
|
8
11
|
"dd-playwright": "bin/cli.mjs",
|
|
9
12
|
"dd-pw": "bin/cli.mjs",
|
package/src/browser-manager.mjs
CHANGED
|
@@ -4,10 +4,36 @@
|
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
5
5
|
import { DatabaseSync } from "node:sqlite";
|
|
6
6
|
import { join } from "node:path";
|
|
7
|
-
import { chromium } from "playwright";
|
|
7
|
+
import { chromium, devices } from "playwright";
|
|
8
8
|
import { ensureChromiumInstalled } from "./browser-check.mjs";
|
|
9
9
|
import { WhistleProxyManager } from "./whistle-proxy.mjs";
|
|
10
10
|
|
|
11
|
+
/** 桌面模式默认视口 */
|
|
12
|
+
const DEFAULT_VIEWPORT = { width: 1280, height: 800 };
|
|
13
|
+
|
|
14
|
+
/** 常用设备描述符示例(用于报错提示) */
|
|
15
|
+
const DEVICE_EXAMPLES = ["iPhone 13", "iPhone 15 Pro", "Pixel 7", "Galaxy S9+", "iPad Mini"];
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 解析设备描述符为 newContext 选项。
|
|
19
|
+
* @param {string|null} device null/"desktop" 表示桌面模式
|
|
20
|
+
* @returns {object} context 选项(桌面模式仅含默认视口)
|
|
21
|
+
*/
|
|
22
|
+
function resolveDeviceOptions(device) {
|
|
23
|
+
if (!device || device === "desktop") {
|
|
24
|
+
return { viewport: DEFAULT_VIEWPORT };
|
|
25
|
+
}
|
|
26
|
+
const descriptor = devices[device];
|
|
27
|
+
if (!descriptor) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`未知设备描述符 [${device}]。取值参考 Playwright devices,例如: ${DEVICE_EXAMPLES.join(", ")};传 "desktop" 恢复桌面模式`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
// 剔除仅用于 launch 的字段,其余(viewport/userAgent/isMobile/hasTouch/deviceScaleFactor 等)全量合入
|
|
33
|
+
const { defaultBrowserType, ...contextOptions } = descriptor;
|
|
34
|
+
return contextOptions;
|
|
35
|
+
}
|
|
36
|
+
|
|
11
37
|
/**
|
|
12
38
|
* 从账号库或文件加载指定账号或激活账号的 storageState。
|
|
13
39
|
* 优先级:
|
|
@@ -89,6 +115,7 @@ export class PlaywrightBrowserManager {
|
|
|
89
115
|
* @param {boolean} [options.headless]
|
|
90
116
|
* @param {string} [options.dbPath]
|
|
91
117
|
* @param {string|null} [options.storageStateFile]
|
|
118
|
+
* @param {string|null} [options.userAgent] 启动级自定义 UA(环境变量),作为会话级 UA 参数的默认值
|
|
92
119
|
* @param {object} [options.storageState] 直接注入,跳过自动加载
|
|
93
120
|
*/
|
|
94
121
|
constructor({
|
|
@@ -97,9 +124,11 @@ export class PlaywrightBrowserManager {
|
|
|
97
124
|
headless = true,
|
|
98
125
|
dbPath,
|
|
99
126
|
storageStateFile = null,
|
|
127
|
+
userAgent = null,
|
|
100
128
|
storageState,
|
|
101
129
|
} = {}) {
|
|
102
130
|
this.headless = headless;
|
|
131
|
+
this.userAgent = userAgent?.trim() || null;
|
|
103
132
|
this.screenshotsDir = screenshotsDir;
|
|
104
133
|
this.dbPath = dbPath;
|
|
105
134
|
this.storageStateFile = storageStateFile;
|
|
@@ -110,6 +139,8 @@ export class PlaywrightBrowserManager {
|
|
|
110
139
|
: resolveStorageState({ dbPath, storageStateFile });
|
|
111
140
|
this.currentUid = null;
|
|
112
141
|
this.isAnonymous = false;
|
|
142
|
+
this.device = null; // 当前设备描述符名;null 表示桌面模式
|
|
143
|
+
this.sessionUserAgent = null; // 会话级 UA(工具参数设置);null 表示回落到 this.userAgent / 设备描述符 / 默认
|
|
113
144
|
this.browser = null;
|
|
114
145
|
this.context = null;
|
|
115
146
|
this.page = null;
|
|
@@ -154,20 +185,39 @@ export class PlaywrightBrowserManager {
|
|
|
154
185
|
|
|
155
186
|
|
|
156
187
|
/**
|
|
157
|
-
* 根据指定的 uid
|
|
158
|
-
* @param {{uid?: string|null, anonymous?: boolean}} [
|
|
188
|
+
* 根据指定的 uid、anonymous 状态、设备描述符与会话级 UA 准备浏览器上下文。
|
|
189
|
+
* @param {{uid?: string|null, anonymous?: boolean, device?: string|null, userAgent?: string|null}} [options]
|
|
190
|
+
* device 不传则保持当前模式;传 "desktop"/null 恢复桌面;传 Playwright 设备名启用移动端仿真。
|
|
191
|
+
* userAgent 不传则保持当前会话级 UA;传 "default"/null 清除会话级 UA(回落到启动配置/设备描述符/默认 UA)
|
|
159
192
|
*/
|
|
160
|
-
async ensurePage({ uid = null, anonymous = false } = {}) {
|
|
193
|
+
async ensurePage({ uid = null, anonymous = false, device, userAgent } = {}) {
|
|
161
194
|
const targetAnonymous = Boolean(anonymous);
|
|
162
195
|
const targetUid = targetAnonymous ? null : (uid ? String(uid) : null);
|
|
163
196
|
|
|
197
|
+
// 先解析并校验设备(非法设备名必须在关闭现有上下文之前抛错,避免误毁会话)
|
|
198
|
+
let targetDevice = this.device;
|
|
199
|
+
if (device !== undefined) {
|
|
200
|
+
targetDevice = !device || device === "desktop" ? null : String(device);
|
|
201
|
+
if (targetDevice) {
|
|
202
|
+
resolveDeviceOptions(targetDevice); // 仅校验,非法名抛错
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const deviceChanged = targetDevice !== this.device;
|
|
206
|
+
|
|
207
|
+
// 解析会话级 UA(同样在任何上下文变更之前完成)
|
|
208
|
+
let targetSessionUA = this.sessionUserAgent;
|
|
209
|
+
if (userAgent !== undefined) {
|
|
210
|
+
targetSessionUA = !userAgent || userAgent === "default" ? null : String(userAgent);
|
|
211
|
+
}
|
|
212
|
+
const uaChanged = targetSessionUA !== this.sessionUserAgent;
|
|
213
|
+
|
|
164
214
|
// 检查是否需要重建上下文(当前模式与目标不一致)
|
|
165
215
|
const authChanged =
|
|
166
216
|
this.isAnonymous !== targetAnonymous ||
|
|
167
217
|
(targetUid !== null && this.currentUid !== targetUid) ||
|
|
168
218
|
(targetUid === null && this.currentUid !== null && !targetAnonymous);
|
|
169
219
|
|
|
170
|
-
if (authChanged && this.context) {
|
|
220
|
+
if ((authChanged || deviceChanged || uaChanged) && this.context) {
|
|
171
221
|
if (this.page && !this.page.isClosed()) {
|
|
172
222
|
await this.page.close().catch(() => {});
|
|
173
223
|
this.page = null;
|
|
@@ -206,10 +256,16 @@ export class PlaywrightBrowserManager {
|
|
|
206
256
|
this.storageState = state;
|
|
207
257
|
this.currentUid = targetUid;
|
|
208
258
|
this.isAnonymous = targetAnonymous;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
259
|
+
this.device = targetDevice;
|
|
260
|
+
this.sessionUserAgent = targetSessionUA;
|
|
261
|
+
|
|
262
|
+
// 对标 Playwright devices 仿真:设备模式下合入 viewport/userAgent/isMobile/hasTouch 等完整描述符
|
|
263
|
+
const contextOptions = resolveDeviceOptions(targetDevice);
|
|
264
|
+
// UA 优先级:会话级(工具参数)> 启动级(环境变量)> 设备描述符 > 浏览器默认
|
|
265
|
+
const effectiveUA = this.sessionUserAgent || this.userAgent;
|
|
266
|
+
if (effectiveUA) {
|
|
267
|
+
contextOptions.userAgent = effectiveUA;
|
|
268
|
+
}
|
|
213
269
|
if (state) {
|
|
214
270
|
contextOptions.storageState = state;
|
|
215
271
|
}
|
|
@@ -247,8 +303,8 @@ export class PlaywrightBrowserManager {
|
|
|
247
303
|
return this.page;
|
|
248
304
|
}
|
|
249
305
|
|
|
250
|
-
async navigate({ url, waitFor, timeout = 15000, uid, anonymous = false }) {
|
|
251
|
-
const page = await this.ensurePage({ uid, anonymous });
|
|
306
|
+
async navigate({ url, waitFor, timeout = 15000, uid, anonymous = false, device, userAgent }) {
|
|
307
|
+
const page = await this.ensurePage({ uid, anonymous, device, userAgent });
|
|
252
308
|
const response = await page.goto(url, {
|
|
253
309
|
timeout,
|
|
254
310
|
waitUntil: "domcontentloaded",
|
|
@@ -491,6 +547,23 @@ export class PlaywrightBrowserManager {
|
|
|
491
547
|
return snapshot;
|
|
492
548
|
}
|
|
493
549
|
|
|
550
|
+
/**
|
|
551
|
+
* 调整当前页面渲染视口(对标 playwright-mcp 的 browser_resize)。
|
|
552
|
+
* 只影响 innerWidth/innerHeight 与布局视口,不改 UA / 触摸仿真;
|
|
553
|
+
* 完整移动端仿真请使用 navigate 的 device 参数。
|
|
554
|
+
* @param {{width: number, height: number}} params
|
|
555
|
+
*/
|
|
556
|
+
async resize({ width, height }) {
|
|
557
|
+
const page = await this.ensurePage();
|
|
558
|
+
const viewport = { width: Number(width), height: Number(height) };
|
|
559
|
+
await page.setViewportSize(viewport);
|
|
560
|
+
return {
|
|
561
|
+
resized: true,
|
|
562
|
+
viewport,
|
|
563
|
+
url: page.url(),
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
494
567
|
async evalJs({ script }) {
|
|
495
568
|
const page = await this.ensurePage();
|
|
496
569
|
const result = await page.evaluate((code) => {
|
package/src/config.mjs
CHANGED
|
@@ -47,6 +47,8 @@ export function resolveConfig(env = process.env) {
|
|
|
47
47
|
domainRegex: buildDomainRegex(domainSuffix),
|
|
48
48
|
headless,
|
|
49
49
|
storageStateFile: env.BROWSER_MCP_STORAGE_STATE?.trim() || null,
|
|
50
|
+
// 启动级自定义 UA;设置后覆盖默认桌面 UA 与 device 设备描述符自带的 UA
|
|
51
|
+
userAgent: env.BROWSER_MCP_USER_AGENT?.trim() || null,
|
|
50
52
|
};
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -390,7 +390,11 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
390
390
|
3) 静态替换:<code>act.tuwan.com/cdn/index.js D:/dist/index.js</code><br>
|
|
391
391
|
4) 请求头注入:<code>y.tuwan.com reqHeaders://{debug: '1'}</code><br>
|
|
392
392
|
👉 官方完整语法参考:<a href="https://wproxy.org/whistle/rules/" target="_blank" style="color: #38bdf8; text-decoration: underline;">https://wproxy.org/whistle/rules/</a><br><br>
|
|
393
|
-
<b>3.
|
|
393
|
+
<b>3. 设备与视口仿真(移动端/H5 验收):</b><br>
|
|
394
|
+
• 移动端验收在导航时传 <code>device</code>(如 <code>"iPhone 13"</code>)启用完整仿真:移动视口 + 移动 UA + 触摸;传 <code>"desktop"</code> 恢复桌面。<br>
|
|
395
|
+
• 自定义 UA 传 <code>userAgent</code>(会话级保持,可与 device 叠加,传 <code>"default"</code> 清除)。<br>
|
|
396
|
+
• 只调视口用 <code>browser_resize({ width, height })</code>;页面内 <code>window.resizeTo</code> 对 Playwright 视口无效。<br><br>
|
|
397
|
+
<b>4. 页面交互规范:</b><br>
|
|
394
398
|
• 复杂或动态页面定位困难时,优先调用 <code>browser_get_dom</code> 获取可见元素清单,严禁盲猜选择器。<br>
|
|
395
399
|
• 表单填写使用 <code>browser_fill_text</code>,按键使用 <code>browser_press_key</code>。
|
|
396
400
|
</div>
|
|
@@ -434,7 +438,21 @@ browser_press_key({ selector: "input#keyword", key: "Enter" })
|
|
|
434
438
|
// 3. 结果截图与控制台报错收集
|
|
435
439
|
browser_screenshot({ selector: ".result-box" })
|
|
436
440
|
const errors = browser_get_console_errors()</div>
|
|
441
|
+
|
|
442
|
+
<div class="code-title"><span>流程 4:移动端 / H5 仿真验收(设备 · UA · 视口)</span></div>
|
|
443
|
+
<div class="code-block" style="color: #93c5fd; font-size: 12px; margin-bottom: 0;">// 1. 完整移动端仿真:视口 390x844 + iPhone UA + 触摸,会话级保持
|
|
444
|
+
browser_navigate({ url: "https://xdd-test.tuwan.com/", device: "iPhone 13" })
|
|
445
|
+
|
|
446
|
+
// 2. 模拟 App 内 WebView:设备仿真 + 自定义 UA(只换 UA,视口/触摸仍按 iPhone 13)
|
|
447
|
+
browser_navigate({ url: "https://xdd-test.tuwan.com/", device: "iPhone 13", userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) TuwanApp/8.0.0" })
|
|
448
|
+
|
|
449
|
+
// 3. 只调视口验证响应式断点(不改 UA / 触摸)
|
|
450
|
+
browser_resize({ width: 375, height: 812 })
|
|
451
|
+
|
|
452
|
+
// 4. 恢复桌面模式并清除自定义 UA
|
|
453
|
+
browser_navigate({ url: "https://xdd-test.tuwan.com/", device: "desktop", userAgent: "default" })</div>
|
|
437
454
|
</div>
|
|
455
|
+
</div>
|
|
438
456
|
</div>
|
|
439
457
|
|
|
440
458
|
<!-- TAB 4: 全局配置 -->
|
|
@@ -476,6 +494,7 @@ const errors = browser_get_console_errors()</div>
|
|
|
476
494
|
</div>
|
|
477
495
|
</div>
|
|
478
496
|
</div>
|
|
497
|
+
</div>
|
|
479
498
|
|
|
480
499
|
<div id="modalOverlay" class="modal-overlay">
|
|
481
500
|
<div class="modal">
|