@xcanwin/manyoyo 5.11.7 → 5.11.9
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/lib/plugin/playwright.js +187 -2
- package/manyoyo.example.json +5 -1
- package/package.json +1 -1
package/lib/plugin/playwright.js
CHANGED
|
@@ -16,7 +16,7 @@ const EXTENSIONS = [
|
|
|
16
16
|
['webgl-fingerprint-defender', 'olnbjpaejebpnokblkepbphhembdicik']
|
|
17
17
|
];
|
|
18
18
|
|
|
19
|
-
const SCENE_ORDER = ['mcp-cont-headless', 'mcp-cont-headed', 'mcp-host-headless', 'mcp-host-headed', 'cli-host-headless', 'cli-host-headed'];
|
|
19
|
+
const SCENE_ORDER = ['mcp-cont-headless', 'mcp-cont-headed', 'mcp-host-headless', 'mcp-host-headed', 'cli-host-headless', 'cli-host-headed', 'dev-host-headed'];
|
|
20
20
|
|
|
21
21
|
const SCENE_DEFS = {
|
|
22
22
|
'mcp-cont-headless': {
|
|
@@ -72,6 +72,12 @@ const SCENE_DEFS = {
|
|
|
72
72
|
portKey: 'cliHostHeaded',
|
|
73
73
|
headless: false,
|
|
74
74
|
listenHost: '0.0.0.0'
|
|
75
|
+
},
|
|
76
|
+
'dev-host-headed': {
|
|
77
|
+
type: 'host',
|
|
78
|
+
engine: 'dev',
|
|
79
|
+
configFile: 'dev-host-headed.json',
|
|
80
|
+
headless: false
|
|
75
81
|
}
|
|
76
82
|
};
|
|
77
83
|
|
|
@@ -96,6 +102,14 @@ function isCliScene(sceneName) {
|
|
|
96
102
|
return Boolean(SCENE_DEFS[sceneName] && SCENE_DEFS[sceneName].engine === 'cli');
|
|
97
103
|
}
|
|
98
104
|
|
|
105
|
+
function isDevScene(sceneName) {
|
|
106
|
+
return Boolean(SCENE_DEFS[sceneName] && SCENE_DEFS[sceneName].engine === 'dev');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isCliSessionScene(sceneName) {
|
|
110
|
+
return isCliScene(sceneName) || isDevScene(sceneName);
|
|
111
|
+
}
|
|
112
|
+
|
|
99
113
|
function platformFromUserAgent(userAgent) {
|
|
100
114
|
const ua = String(userAgent || '').toLowerCase();
|
|
101
115
|
if (ua.includes('macintosh') || ua.includes('mac os x')) {
|
|
@@ -154,6 +168,20 @@ function asBoolean(value, fallback = false) {
|
|
|
154
168
|
return fallback;
|
|
155
169
|
}
|
|
156
170
|
|
|
171
|
+
function uniqueStrings(values) {
|
|
172
|
+
const out = [];
|
|
173
|
+
const seen = new Set();
|
|
174
|
+
for (const value of values) {
|
|
175
|
+
const text = String(value || '').trim();
|
|
176
|
+
if (!text || seen.has(text)) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
seen.add(text);
|
|
180
|
+
out.push(text);
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
|
|
157
185
|
function isHostPermission(value) {
|
|
158
186
|
if (value === '<all_urls>') {
|
|
159
187
|
return true;
|
|
@@ -305,6 +333,8 @@ class PlaywrightPlugin {
|
|
|
305
333
|
extensionProdversion: '132.0.0.0',
|
|
306
334
|
navigatorPlatform: platformFromUserAgent(DEFAULT_FINGERPRINT_PROFILE.userAgent),
|
|
307
335
|
disableWebRTC: false,
|
|
336
|
+
devtoolsActivePortPath: '',
|
|
337
|
+
devtoolsCdpTimeout: 60000,
|
|
308
338
|
composeDir: path.join(__dirname, 'playwright-assets'),
|
|
309
339
|
ports: {
|
|
310
340
|
mcpContHeadless: 8931,
|
|
@@ -350,7 +380,10 @@ class PlaywrightPlugin {
|
|
|
350
380
|
if (invalidScene) {
|
|
351
381
|
throw new Error(`playwright.enabledScenes 包含未知场景: ${invalidScene}`);
|
|
352
382
|
}
|
|
353
|
-
|
|
383
|
+
merged.devtoolsActivePortPath = String(merged.devtoolsActivePortPath || '').trim();
|
|
384
|
+
merged.devtoolsCdpTimeout = Number(merged.devtoolsCdpTimeout) || defaultConfig.devtoolsCdpTimeout;
|
|
385
|
+
|
|
386
|
+
if (merged.cliSessionScene && !isCliSessionScene(merged.cliSessionScene)) {
|
|
354
387
|
throw new Error(`playwright.cliSessionScene 无效: ${merged.cliSessionScene}`);
|
|
355
388
|
}
|
|
356
389
|
|
|
@@ -509,6 +542,62 @@ class PlaywrightPlugin {
|
|
|
509
542
|
fs.rmSync(this.sceneCliAttachConfigPath(sceneName), { force: true });
|
|
510
543
|
}
|
|
511
544
|
|
|
545
|
+
devtoolsActivePortCandidates() {
|
|
546
|
+
const homeDir = this.config.homeDir;
|
|
547
|
+
const configured = this.config.devtoolsActivePortPath;
|
|
548
|
+
return uniqueStrings([
|
|
549
|
+
configured,
|
|
550
|
+
path.join(homeDir, 'Library', 'Application Support', 'Google', 'Chrome', 'DevToolsActivePort'),
|
|
551
|
+
path.join(homeDir, 'Library', 'Application Support', 'Google', 'Chrome Canary', 'DevToolsActivePort'),
|
|
552
|
+
path.join(homeDir, 'Library', 'Application Support', 'Chromium', 'DevToolsActivePort'),
|
|
553
|
+
path.join(homeDir, 'Library', 'Application Support', 'BraveSoftware', 'Brave-Browser', 'DevToolsActivePort'),
|
|
554
|
+
path.join(homeDir, '.config', 'google-chrome', 'DevToolsActivePort'),
|
|
555
|
+
path.join(homeDir, '.config', 'chromium', 'DevToolsActivePort'),
|
|
556
|
+
path.join(homeDir, '.config', 'google-chrome-beta', 'DevToolsActivePort'),
|
|
557
|
+
path.join(homeDir, '.config', 'google-chrome-unstable', 'DevToolsActivePort'),
|
|
558
|
+
path.join(homeDir, '.config', 'BraveSoftware', 'Brave-Browser', 'DevToolsActivePort'),
|
|
559
|
+
path.join(homeDir, 'AppData', 'Local', 'Google', 'Chrome', 'User Data', 'DevToolsActivePort'),
|
|
560
|
+
path.join(homeDir, 'AppData', 'Local', 'Google', 'Chrome Beta', 'User Data', 'DevToolsActivePort'),
|
|
561
|
+
path.join(homeDir, 'AppData', 'Local', 'Google', 'Chrome SxS', 'User Data', 'DevToolsActivePort'),
|
|
562
|
+
path.join(homeDir, 'AppData', 'Local', 'Chromium', 'User Data', 'DevToolsActivePort'),
|
|
563
|
+
path.join(homeDir, 'AppData', 'Local', 'BraveSoftware', 'Brave-Browser', 'User Data', 'DevToolsActivePort')
|
|
564
|
+
]);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
parseDevToolsActivePort(content, filePath = 'DevToolsActivePort') {
|
|
568
|
+
const lines = String(content || '')
|
|
569
|
+
.split(/\r?\n/)
|
|
570
|
+
.map(line => line.trim())
|
|
571
|
+
.filter(Boolean);
|
|
572
|
+
const port = Number(lines[0]);
|
|
573
|
+
const wsPath = lines[1] || '';
|
|
574
|
+
|
|
575
|
+
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
|
576
|
+
throw new Error(`${filePath} 内容无效: 第一行必须是端口号`);
|
|
577
|
+
}
|
|
578
|
+
if (!wsPath.startsWith('/devtools/browser/')) {
|
|
579
|
+
throw new Error(`${filePath} 内容无效: 第二行必须是 /devtools/browser/...`);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return { port, wsPath };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
readDevToolsActivePort() {
|
|
586
|
+
const candidates = this.devtoolsActivePortCandidates();
|
|
587
|
+
for (const candidate of candidates) {
|
|
588
|
+
if (!fs.existsSync(candidate)) {
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
const content = fs.readFileSync(candidate, 'utf8');
|
|
592
|
+
return {
|
|
593
|
+
...this.parseDevToolsActivePort(content, candidate),
|
|
594
|
+
filePath: candidate
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
throw new Error(`未找到 DevToolsActivePort,请先在 Chrome 启用 remote debugging,并挂载或配置 devtoolsActivePortPath。已检查: ${candidates.join(', ')}`);
|
|
599
|
+
}
|
|
600
|
+
|
|
512
601
|
sceneInitScriptPath(sceneName) {
|
|
513
602
|
const configFile = path.basename(this.sceneConfigPath(sceneName), '.json');
|
|
514
603
|
return path.join(this.config.configDir, `${configFile}.init.js`);
|
|
@@ -988,6 +1077,10 @@ class PlaywrightPlugin {
|
|
|
988
1077
|
return { envEntries: [], extraArgs: [], volumeEntries: [] };
|
|
989
1078
|
}
|
|
990
1079
|
|
|
1080
|
+
if (isDevScene(sceneName)) {
|
|
1081
|
+
return this.buildDevHostCliSessionIntegration(dockerCmd, sceneName);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
991
1084
|
const endpoint = this.readSceneEndpoint(sceneName);
|
|
992
1085
|
if (!endpoint || !Number.isInteger(endpoint.port) || endpoint.port <= 0 || typeof endpoint.wsPath !== 'string' || !endpoint.wsPath) {
|
|
993
1086
|
return { envEntries: [], extraArgs: [], volumeEntries: [] };
|
|
@@ -999,6 +1092,7 @@ class PlaywrightPlugin {
|
|
|
999
1092
|
const hostConfigPath = this.sceneCliAttachConfigPath(sceneName);
|
|
1000
1093
|
const containerConfigPath = `/tmp/manyoyo-playwright/${sceneName}.cli-attach.json`;
|
|
1001
1094
|
this.writeSceneCliAttachConfig(sceneName, {
|
|
1095
|
+
outputDir: '/tmp/.playwright-cli',
|
|
1002
1096
|
browser: {
|
|
1003
1097
|
// launch-server(非 shared)不预创建 context;remoteEndpoint 存在时
|
|
1004
1098
|
// playwright-cli 默认 isolated=false 会走 contexts()[0] 并报
|
|
@@ -1018,6 +1112,36 @@ class PlaywrightPlugin {
|
|
|
1018
1112
|
return { envEntries, extraArgs, volumeEntries };
|
|
1019
1113
|
}
|
|
1020
1114
|
|
|
1115
|
+
buildDevHostCliSessionIntegration(dockerCmd, sceneName) {
|
|
1116
|
+
const endpoint = this.readDevToolsActivePort();
|
|
1117
|
+
const normalizedDockerCmd = String(dockerCmd || '').trim().toLowerCase();
|
|
1118
|
+
const connectHost = normalizedDockerCmd === 'podman' ? 'host.containers.internal' : 'host.docker.internal';
|
|
1119
|
+
const cdpEndpoint = `ws://${connectHost}:${endpoint.port}${endpoint.wsPath}`;
|
|
1120
|
+
const hostConfigPath = this.sceneCliAttachConfigPath(sceneName);
|
|
1121
|
+
const containerConfigPath = `/tmp/manyoyo-playwright/${sceneName}.cli-attach.json`;
|
|
1122
|
+
|
|
1123
|
+
this.writeSceneCliAttachConfig(sceneName, {
|
|
1124
|
+
outputDir: '/tmp/.playwright-cli',
|
|
1125
|
+
browser: {
|
|
1126
|
+
cdpEndpoint,
|
|
1127
|
+
cdpHeaders: {
|
|
1128
|
+
Host: `127.0.0.1:${endpoint.port}`
|
|
1129
|
+
},
|
|
1130
|
+
cdpTimeout: this.config.devtoolsCdpTimeout
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
const envEntries = [
|
|
1135
|
+
`PLAYWRIGHT_MCP_CONFIG=${containerConfigPath}`,
|
|
1136
|
+
'PW_CHROMIUM_ATTACH_TO_OTHER=1'
|
|
1137
|
+
];
|
|
1138
|
+
const extraArgs = normalizedDockerCmd === 'docker'
|
|
1139
|
+
? ['--add-host', 'host.docker.internal:host-gateway']
|
|
1140
|
+
: [];
|
|
1141
|
+
const volumeEntries = ['--volume', `${hostConfigPath}:${containerConfigPath}:ro`];
|
|
1142
|
+
return { envEntries, extraArgs, volumeEntries };
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1021
1145
|
async startContainer(sceneName, options = {}) {
|
|
1022
1146
|
const runtime = this.config.containerRuntime;
|
|
1023
1147
|
if (!this.ensureCommandAvailable(runtime)) {
|
|
@@ -1323,6 +1447,17 @@ class PlaywrightPlugin {
|
|
|
1323
1447
|
return 1;
|
|
1324
1448
|
}
|
|
1325
1449
|
|
|
1450
|
+
startDevHost(sceneName) {
|
|
1451
|
+
try {
|
|
1452
|
+
const endpoint = this.readDevToolsActivePort();
|
|
1453
|
+
this.writeStdout(`[up] ${sceneName} ready via DevToolsActivePort (${endpoint.filePath}, 127.0.0.1:${endpoint.port})`);
|
|
1454
|
+
return 0;
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
this.writeStderr(`[up] ${sceneName} failed: ${error.message || String(error)}`);
|
|
1457
|
+
return 1;
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1326
1461
|
async stopHost(sceneName) {
|
|
1327
1462
|
const pidFile = this.scenePidFile(sceneName);
|
|
1328
1463
|
const port = this.scenePort(sceneName);
|
|
@@ -1363,6 +1498,13 @@ class PlaywrightPlugin {
|
|
|
1363
1498
|
return 0;
|
|
1364
1499
|
}
|
|
1365
1500
|
|
|
1501
|
+
stopDevHost(sceneName) {
|
|
1502
|
+
this.removeSceneEndpoint(sceneName);
|
|
1503
|
+
this.removeSceneCliAttachConfig(sceneName);
|
|
1504
|
+
this.writeStdout(`[down] ${sceneName} no managed process`);
|
|
1505
|
+
return 0;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1366
1508
|
async statusHost(sceneName) {
|
|
1367
1509
|
const pidFile = this.scenePidFile(sceneName);
|
|
1368
1510
|
const port = this.scenePort(sceneName);
|
|
@@ -1392,7 +1534,28 @@ class PlaywrightPlugin {
|
|
|
1392
1534
|
return 0;
|
|
1393
1535
|
}
|
|
1394
1536
|
|
|
1537
|
+
statusDevHost(sceneName) {
|
|
1538
|
+
try {
|
|
1539
|
+
const endpoint = this.readDevToolsActivePort();
|
|
1540
|
+
this.writeStdout(`[status] ${sceneName} configured (${endpoint.filePath}, 127.0.0.1:${endpoint.port})`);
|
|
1541
|
+
} catch {
|
|
1542
|
+
this.writeStdout(`[status] ${sceneName} not configured`);
|
|
1543
|
+
}
|
|
1544
|
+
return 0;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1395
1547
|
async healthScene(sceneName) {
|
|
1548
|
+
if (isDevScene(sceneName)) {
|
|
1549
|
+
try {
|
|
1550
|
+
const endpoint = this.readDevToolsActivePort();
|
|
1551
|
+
this.writeStdout(`[health] ${sceneName} ok (DevToolsActivePort 127.0.0.1:${endpoint.port})`);
|
|
1552
|
+
return 0;
|
|
1553
|
+
} catch (error) {
|
|
1554
|
+
this.writeStdout(`[health] ${sceneName} fail (${error.message || String(error)})`);
|
|
1555
|
+
return 1;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1396
1559
|
const port = this.scenePort(sceneName);
|
|
1397
1560
|
if (await this.portReady(port)) {
|
|
1398
1561
|
this.writeStdout(`[health] ${sceneName} ok (127.0.0.1:${port})`);
|
|
@@ -1403,6 +1566,11 @@ class PlaywrightPlugin {
|
|
|
1403
1566
|
}
|
|
1404
1567
|
|
|
1405
1568
|
logsHost(sceneName) {
|
|
1569
|
+
if (isDevScene(sceneName)) {
|
|
1570
|
+
this.writeStdout(`[logs] ${sceneName} no managed process`);
|
|
1571
|
+
return 0;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1406
1574
|
const logFile = this.sceneLogFile(sceneName);
|
|
1407
1575
|
if (!fs.existsSync(logFile)) {
|
|
1408
1576
|
this.writeStdout(`[logs] ${sceneName} no log file: ${logFile}`);
|
|
@@ -1613,6 +1781,23 @@ class PlaywrightPlugin {
|
|
|
1613
1781
|
|
|
1614
1782
|
async runOnScene(action, sceneName, options = {}) {
|
|
1615
1783
|
const def = SCENE_DEFS[sceneName];
|
|
1784
|
+
if (isDevScene(sceneName)) {
|
|
1785
|
+
if (action === 'up') {
|
|
1786
|
+
return this.startDevHost(sceneName);
|
|
1787
|
+
}
|
|
1788
|
+
if (action === 'down') {
|
|
1789
|
+
return this.stopDevHost(sceneName);
|
|
1790
|
+
}
|
|
1791
|
+
if (action === 'status') {
|
|
1792
|
+
return this.statusDevHost(sceneName);
|
|
1793
|
+
}
|
|
1794
|
+
if (action === 'health') {
|
|
1795
|
+
return await this.healthScene(sceneName);
|
|
1796
|
+
}
|
|
1797
|
+
if (action === 'logs') {
|
|
1798
|
+
return this.logsHost(sceneName);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1616
1801
|
if (action === 'up') {
|
|
1617
1802
|
return def.type === 'container'
|
|
1618
1803
|
? await this.startContainer(sceneName, options)
|
package/manyoyo.example.json
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
// mixed: 支持容器+宿主机;container: 仅容器;host: 仅宿主机
|
|
51
51
|
"runtime": "mixed",
|
|
52
52
|
// 启用场景(可按需裁剪)
|
|
53
|
-
"enabledScenes": ["mcp-cont-headless", "mcp-cont-headed", "mcp-host-headless", "mcp-host-headed", "cli-host-headless", "cli-host-headed"],
|
|
53
|
+
"enabledScenes": ["mcp-cont-headless", "mcp-cont-headed", "mcp-host-headless", "mcp-host-headed", "cli-host-headless", "cli-host-headed", "dev-host-headed"],
|
|
54
54
|
// my run 默认注入的 playwright-cli 宿主场景
|
|
55
55
|
"cliSessionScene": "cli-host-headless",
|
|
56
56
|
// mcp-add 默认 host(可改为 localhost / 127.0.0.1)
|
|
@@ -63,6 +63,10 @@
|
|
|
63
63
|
"navigatorPlatform": "MacIntel",
|
|
64
64
|
// 是否禁用 WebRTC(默认 false)
|
|
65
65
|
"disableWebRTC": false,
|
|
66
|
+
// dev-host-headed 读取的 Chrome DevToolsActivePort 文件;留空时自动探测常见路径
|
|
67
|
+
"devtoolsActivePortPath": "",
|
|
68
|
+
// dev-host-headed 连接 Chrome CDP 的超时时间(毫秒)
|
|
69
|
+
"devtoolsCdpTimeout": 60000,
|
|
66
70
|
"ports": {
|
|
67
71
|
"mcpContHeadless": 8931,
|
|
68
72
|
"mcpContHeaded": 8932,
|