@xcanwin/manyoyo 5.11.6 → 5.11.8
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 +191 -3
- 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: [] };
|
|
@@ -1000,7 +1093,12 @@ class PlaywrightPlugin {
|
|
|
1000
1093
|
const containerConfigPath = `/tmp/manyoyo-playwright/${sceneName}.cli-attach.json`;
|
|
1001
1094
|
this.writeSceneCliAttachConfig(sceneName, {
|
|
1002
1095
|
browser: {
|
|
1003
|
-
remoteEndpoint
|
|
1096
|
+
// launch-server(非 shared)不预创建 context;remoteEndpoint 存在时
|
|
1097
|
+
// playwright-cli 默认 isolated=false 会走 contexts()[0] 并报
|
|
1098
|
+
// "unable to connect to a browser that does not have any contexts",
|
|
1099
|
+
// 因此容器内会话必须以 isolated 模式自建 context。
|
|
1100
|
+
remoteEndpoint,
|
|
1101
|
+
isolated: true
|
|
1004
1102
|
}
|
|
1005
1103
|
});
|
|
1006
1104
|
const envEntries = [
|
|
@@ -1013,6 +1111,35 @@ class PlaywrightPlugin {
|
|
|
1013
1111
|
return { envEntries, extraArgs, volumeEntries };
|
|
1014
1112
|
}
|
|
1015
1113
|
|
|
1114
|
+
buildDevHostCliSessionIntegration(dockerCmd, sceneName) {
|
|
1115
|
+
const endpoint = this.readDevToolsActivePort();
|
|
1116
|
+
const normalizedDockerCmd = String(dockerCmd || '').trim().toLowerCase();
|
|
1117
|
+
const connectHost = normalizedDockerCmd === 'podman' ? 'host.containers.internal' : 'host.docker.internal';
|
|
1118
|
+
const cdpEndpoint = `ws://${connectHost}:${endpoint.port}${endpoint.wsPath}`;
|
|
1119
|
+
const hostConfigPath = this.sceneCliAttachConfigPath(sceneName);
|
|
1120
|
+
const containerConfigPath = `/tmp/manyoyo-playwright/${sceneName}.cli-attach.json`;
|
|
1121
|
+
|
|
1122
|
+
this.writeSceneCliAttachConfig(sceneName, {
|
|
1123
|
+
browser: {
|
|
1124
|
+
cdpEndpoint,
|
|
1125
|
+
cdpHeaders: {
|
|
1126
|
+
Host: `127.0.0.1:${endpoint.port}`
|
|
1127
|
+
},
|
|
1128
|
+
cdpTimeout: this.config.devtoolsCdpTimeout
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
const envEntries = [
|
|
1133
|
+
`PLAYWRIGHT_MCP_CONFIG=${containerConfigPath}`,
|
|
1134
|
+
'PW_CHROMIUM_ATTACH_TO_OTHER=1'
|
|
1135
|
+
];
|
|
1136
|
+
const extraArgs = normalizedDockerCmd === 'docker'
|
|
1137
|
+
? ['--add-host', 'host.docker.internal:host-gateway']
|
|
1138
|
+
: [];
|
|
1139
|
+
const volumeEntries = ['--volume', `${hostConfigPath}:${containerConfigPath}:ro`];
|
|
1140
|
+
return { envEntries, extraArgs, volumeEntries };
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1016
1143
|
async startContainer(sceneName, options = {}) {
|
|
1017
1144
|
const runtime = this.config.containerRuntime;
|
|
1018
1145
|
if (!this.ensureCommandAvailable(runtime)) {
|
|
@@ -1318,6 +1445,17 @@ class PlaywrightPlugin {
|
|
|
1318
1445
|
return 1;
|
|
1319
1446
|
}
|
|
1320
1447
|
|
|
1448
|
+
startDevHost(sceneName) {
|
|
1449
|
+
try {
|
|
1450
|
+
const endpoint = this.readDevToolsActivePort();
|
|
1451
|
+
this.writeStdout(`[up] ${sceneName} ready via DevToolsActivePort (${endpoint.filePath}, 127.0.0.1:${endpoint.port})`);
|
|
1452
|
+
return 0;
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
this.writeStderr(`[up] ${sceneName} failed: ${error.message || String(error)}`);
|
|
1455
|
+
return 1;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1321
1459
|
async stopHost(sceneName) {
|
|
1322
1460
|
const pidFile = this.scenePidFile(sceneName);
|
|
1323
1461
|
const port = this.scenePort(sceneName);
|
|
@@ -1358,6 +1496,13 @@ class PlaywrightPlugin {
|
|
|
1358
1496
|
return 0;
|
|
1359
1497
|
}
|
|
1360
1498
|
|
|
1499
|
+
stopDevHost(sceneName) {
|
|
1500
|
+
this.removeSceneEndpoint(sceneName);
|
|
1501
|
+
this.removeSceneCliAttachConfig(sceneName);
|
|
1502
|
+
this.writeStdout(`[down] ${sceneName} no managed process`);
|
|
1503
|
+
return 0;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1361
1506
|
async statusHost(sceneName) {
|
|
1362
1507
|
const pidFile = this.scenePidFile(sceneName);
|
|
1363
1508
|
const port = this.scenePort(sceneName);
|
|
@@ -1387,7 +1532,28 @@ class PlaywrightPlugin {
|
|
|
1387
1532
|
return 0;
|
|
1388
1533
|
}
|
|
1389
1534
|
|
|
1535
|
+
statusDevHost(sceneName) {
|
|
1536
|
+
try {
|
|
1537
|
+
const endpoint = this.readDevToolsActivePort();
|
|
1538
|
+
this.writeStdout(`[status] ${sceneName} configured (${endpoint.filePath}, 127.0.0.1:${endpoint.port})`);
|
|
1539
|
+
} catch {
|
|
1540
|
+
this.writeStdout(`[status] ${sceneName} not configured`);
|
|
1541
|
+
}
|
|
1542
|
+
return 0;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1390
1545
|
async healthScene(sceneName) {
|
|
1546
|
+
if (isDevScene(sceneName)) {
|
|
1547
|
+
try {
|
|
1548
|
+
const endpoint = this.readDevToolsActivePort();
|
|
1549
|
+
this.writeStdout(`[health] ${sceneName} ok (DevToolsActivePort 127.0.0.1:${endpoint.port})`);
|
|
1550
|
+
return 0;
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
this.writeStdout(`[health] ${sceneName} fail (${error.message || String(error)})`);
|
|
1553
|
+
return 1;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1391
1557
|
const port = this.scenePort(sceneName);
|
|
1392
1558
|
if (await this.portReady(port)) {
|
|
1393
1559
|
this.writeStdout(`[health] ${sceneName} ok (127.0.0.1:${port})`);
|
|
@@ -1398,6 +1564,11 @@ class PlaywrightPlugin {
|
|
|
1398
1564
|
}
|
|
1399
1565
|
|
|
1400
1566
|
logsHost(sceneName) {
|
|
1567
|
+
if (isDevScene(sceneName)) {
|
|
1568
|
+
this.writeStdout(`[logs] ${sceneName} no managed process`);
|
|
1569
|
+
return 0;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1401
1572
|
const logFile = this.sceneLogFile(sceneName);
|
|
1402
1573
|
if (!fs.existsSync(logFile)) {
|
|
1403
1574
|
this.writeStdout(`[logs] ${sceneName} no log file: ${logFile}`);
|
|
@@ -1608,6 +1779,23 @@ class PlaywrightPlugin {
|
|
|
1608
1779
|
|
|
1609
1780
|
async runOnScene(action, sceneName, options = {}) {
|
|
1610
1781
|
const def = SCENE_DEFS[sceneName];
|
|
1782
|
+
if (isDevScene(sceneName)) {
|
|
1783
|
+
if (action === 'up') {
|
|
1784
|
+
return this.startDevHost(sceneName);
|
|
1785
|
+
}
|
|
1786
|
+
if (action === 'down') {
|
|
1787
|
+
return this.stopDevHost(sceneName);
|
|
1788
|
+
}
|
|
1789
|
+
if (action === 'status') {
|
|
1790
|
+
return this.statusDevHost(sceneName);
|
|
1791
|
+
}
|
|
1792
|
+
if (action === 'health') {
|
|
1793
|
+
return await this.healthScene(sceneName);
|
|
1794
|
+
}
|
|
1795
|
+
if (action === 'logs') {
|
|
1796
|
+
return this.logsHost(sceneName);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1611
1799
|
if (action === 'up') {
|
|
1612
1800
|
return def.type === 'container'
|
|
1613
1801
|
? 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,
|