@xcanwin/manyoyo 5.11.7 → 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 +185 -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: [] };
|
|
@@ -1018,6 +1111,35 @@ class PlaywrightPlugin {
|
|
|
1018
1111
|
return { envEntries, extraArgs, volumeEntries };
|
|
1019
1112
|
}
|
|
1020
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
|
+
|
|
1021
1143
|
async startContainer(sceneName, options = {}) {
|
|
1022
1144
|
const runtime = this.config.containerRuntime;
|
|
1023
1145
|
if (!this.ensureCommandAvailable(runtime)) {
|
|
@@ -1323,6 +1445,17 @@ class PlaywrightPlugin {
|
|
|
1323
1445
|
return 1;
|
|
1324
1446
|
}
|
|
1325
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
|
+
|
|
1326
1459
|
async stopHost(sceneName) {
|
|
1327
1460
|
const pidFile = this.scenePidFile(sceneName);
|
|
1328
1461
|
const port = this.scenePort(sceneName);
|
|
@@ -1363,6 +1496,13 @@ class PlaywrightPlugin {
|
|
|
1363
1496
|
return 0;
|
|
1364
1497
|
}
|
|
1365
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
|
+
|
|
1366
1506
|
async statusHost(sceneName) {
|
|
1367
1507
|
const pidFile = this.scenePidFile(sceneName);
|
|
1368
1508
|
const port = this.scenePort(sceneName);
|
|
@@ -1392,7 +1532,28 @@ class PlaywrightPlugin {
|
|
|
1392
1532
|
return 0;
|
|
1393
1533
|
}
|
|
1394
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
|
+
|
|
1395
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
|
+
|
|
1396
1557
|
const port = this.scenePort(sceneName);
|
|
1397
1558
|
if (await this.portReady(port)) {
|
|
1398
1559
|
this.writeStdout(`[health] ${sceneName} ok (127.0.0.1:${port})`);
|
|
@@ -1403,6 +1564,11 @@ class PlaywrightPlugin {
|
|
|
1403
1564
|
}
|
|
1404
1565
|
|
|
1405
1566
|
logsHost(sceneName) {
|
|
1567
|
+
if (isDevScene(sceneName)) {
|
|
1568
|
+
this.writeStdout(`[logs] ${sceneName} no managed process`);
|
|
1569
|
+
return 0;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1406
1572
|
const logFile = this.sceneLogFile(sceneName);
|
|
1407
1573
|
if (!fs.existsSync(logFile)) {
|
|
1408
1574
|
this.writeStdout(`[logs] ${sceneName} no log file: ${logFile}`);
|
|
@@ -1613,6 +1779,23 @@ class PlaywrightPlugin {
|
|
|
1613
1779
|
|
|
1614
1780
|
async runOnScene(action, sceneName, options = {}) {
|
|
1615
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
|
+
}
|
|
1616
1799
|
if (action === 'up') {
|
|
1617
1800
|
return def.type === 'container'
|
|
1618
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,
|