@web-auto/webauto 0.1.3 → 0.1.6
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/apps/desktop-console/default-settings.json +2 -2
- package/apps/desktop-console/dist/main/index.mjs +915 -85
- package/apps/desktop-console/dist/main/preload.mjs +7 -0
- package/apps/desktop-console/dist/renderer/index.html +622 -50
- package/apps/desktop-console/dist/renderer/index.js +2415 -470
- package/apps/desktop-console/dist/renderer/run.mts +6 -5
- package/apps/desktop-console/entry/ui-cli.mjs +672 -0
- package/apps/desktop-console/entry/ui-console.mjs +416 -29
- package/apps/webauto/entry/account.mjs +89 -53
- package/apps/webauto/entry/browser-status.mjs +7 -10
- package/apps/webauto/entry/lib/account-detect.mjs +254 -28
- package/apps/webauto/entry/lib/account-store.mjs +219 -30
- package/apps/webauto/entry/lib/bus-publish.mjs +63 -0
- package/apps/webauto/entry/lib/camo-cli.mjs +93 -0
- package/apps/webauto/entry/lib/profilepool.mjs +14 -5
- package/apps/webauto/entry/lib/quota-status.mjs +23 -0
- package/apps/webauto/entry/lib/schedule-store.mjs +1068 -0
- package/apps/webauto/entry/profilepool.mjs +106 -17
- package/apps/webauto/entry/schedule.mjs +612 -0
- package/apps/webauto/entry/weibo-unified.mjs +134 -0
- package/apps/webauto/entry/xhs-install.mjs +236 -29
- package/apps/webauto/entry/xhs-status.mjs +5 -2
- package/apps/webauto/entry/xhs-unified.mjs +631 -98
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/comment_item/container.json +40 -0
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_expand_button/container.json +38 -0
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_list/container.json +37 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/container.json +8 -3
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/login_anchor/container.json +30 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_bar/container.json +47 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_button/container.json +39 -0
- package/bin/camoufox-cli.mjs +61 -0
- package/bin/webauto.mjs +301 -54
- package/dist/modules/camo-backend/src/index.js +49 -1
- package/dist/modules/camo-backend/src/internal/BrowserSession.js +572 -3
- package/dist/modules/camo-backend/src/internal/SessionManager.js +13 -1
- package/dist/modules/camo-backend/src/internal/storage-paths.js +6 -0
- package/dist/modules/collection-manager/bloom-filter.js +91 -0
- package/dist/modules/collection-manager/date-utils.js +275 -0
- package/dist/modules/collection-manager/index.js +258 -0
- package/dist/modules/collection-manager/storage.js +195 -0
- package/dist/modules/collection-manager/types.js +47 -0
- package/dist/modules/logging/src/index.js +1 -1
- package/dist/modules/process-registry/index.js +230 -0
- package/dist/modules/rate-limiter/index.js +242 -0
- package/dist/modules/workflow/blocks/ExecuteWeiboSearchBlock.js +128 -0
- package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +7 -3
- package/dist/modules/workflow/blocks/RenderMarkdown.js +4 -1
- package/dist/modules/workflow/blocks/WeiboCollectCommentsBlock.js +282 -0
- package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +283 -0
- package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +208 -0
- package/dist/modules/workflow/blocks/WeiboCollectTimelineListBlock.js +128 -0
- package/dist/modules/workflow/blocks/WeiboCollectUserPostsListBlock.js +127 -0
- package/dist/modules/workflow/blocks/helpers/downloadPaths.js +21 -0
- package/dist/modules/workflow/config/workflowRegistry.js +2 -0
- package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +47 -0
- package/dist/modules/workflow/src/runner.js +6 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +4 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +2 -2
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +123 -0
- package/dist/modules/xiaohongshu/app/src/container-registry/src/index.d.ts +37 -0
- package/dist/modules/xiaohongshu/app/src/container-registry/src/index.js +184 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.d.ts +31 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.js +71 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.d.ts +48 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.js +259 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.d.ts +28 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.js +319 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.d.ts +36 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.js +162 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.d.ts +36 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.js +301 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.d.ts +29 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.js +195 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.d.ts +25 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.js +164 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.d.ts +66 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.d.ts +16 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.d.ts +27 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.d.ts +18 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.d.ts +34 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.d.ts +17 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.d.ts +15 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.d.ts +26 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.d.ts +29 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.d.ts +38 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.d.ts +30 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.d.ts +23 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.d.ts +32 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.d.ts +35 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.d.ts +34 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.d.ts +111 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.d.ts +20 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.d.ts +48 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.d.ts +23 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.d.ts +55 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.d.ts +21 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.d.ts +5 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.d.ts +37 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.js +165 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.d.ts +33 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.d.ts +9 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.js +9 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.d.ts +50 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.js +222 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.d.ts +10 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.js +43 -0
- package/dist/services/shared/serviceProcessLogger.js +1 -1
- package/dist/services/unified-api/server.js +105 -11
- package/modules/camo-backend/src/index.ts +46 -1
- package/modules/camo-backend/src/internal/BrowserSession.ts +619 -3
- package/modules/camo-backend/src/internal/SessionManager.ts +12 -1
- package/modules/camo-backend/src/internal/storage-paths.ts +5 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +38 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +47 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +94 -11
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +208 -2
- package/modules/camo-runtime/src/autoscript/runtime.mjs +7 -1
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +76 -43
- package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +75 -1
- package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +71 -4
- package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +183 -27
- package/modules/collection-manager/bloom-filter.ts +112 -0
- package/modules/collection-manager/date-utils.ts +316 -0
- package/modules/collection-manager/index.ts +309 -0
- package/modules/collection-manager/package.json +10 -0
- package/modules/collection-manager/storage.ts +174 -0
- package/modules/collection-manager/types.ts +156 -0
- package/modules/logging/src/index.ts +1 -1
- package/modules/process-registry/index.ts +284 -0
- package/modules/rate-limiter/index.ts +322 -0
- package/modules/state/src/paths.ts +9 -1
- package/modules/task-scheduler/index.ts +293 -0
- package/modules/workflow/blocks/ExecuteWeiboSearchBlock.ts +167 -0
- package/modules/workflow/blocks/PersistXhsNoteBlock.ts +7 -3
- package/modules/workflow/blocks/RenderMarkdown.ts +4 -1
- package/modules/workflow/blocks/WeiboCollectCommentsBlock.ts +339 -0
- package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +338 -0
- package/modules/workflow/blocks/helpers/downloadPaths.ts +16 -0
- package/modules/workflow/config/workflowRegistry.ts +2 -0
- package/modules/workflow/definitions/weibo-search-workflow-v1.ts +47 -0
- package/modules/workflow/src/runner.ts +6 -0
- package/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.ts +1 -1
- package/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.ts +4 -0
- package/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.ts +2 -3
- package/modules/xiaohongshu/app/src/blocks/helpers/sharding.ts +152 -0
- package/package.json +14 -5
- package/scripts/postinstall-resources.mjs +62 -0
- package/scripts/test/run-coverage.mjs +76 -0
- package/scripts/weibo/search.ts +49 -0
- package/services/shared/serviceProcessLogger.ts +1 -1
- package/services/unified-api/server.ts +98 -12
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import minimist from 'minimist';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const APP_ROOT = path.resolve(__dirname, '..');
|
|
11
|
+
const ROOT = path.resolve(APP_ROOT, '..', '..');
|
|
12
|
+
const CONTROL_FILE = path.join(os.homedir(), '.webauto', 'run', 'ui-cli.json');
|
|
13
|
+
const DEFAULT_HOST = process.env.WEBAUTO_UI_CLI_HOST || '127.0.0.1';
|
|
14
|
+
const DEFAULT_PORT = Number(process.env.WEBAUTO_UI_CLI_PORT || 7716);
|
|
15
|
+
|
|
16
|
+
const args = minimist(process.argv.slice(2), {
|
|
17
|
+
boolean: ['help', 'json', 'auto-start', 'build', 'install', 'continue-on-error', 'exact', 'keep-open', 'detailed'],
|
|
18
|
+
string: ['host', 'port', 'selector', 'value', 'text', 'key', 'tab', 'label', 'state', 'file', 'output', 'timeout', 'interval', 'nth'],
|
|
19
|
+
alias: { h: 'help' },
|
|
20
|
+
default: { 'auto-start': false, json: false, 'keep-open': false },
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function printHelp() {
|
|
24
|
+
console.log(`webauto ui cli
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
webauto ui cli start [--build] [--install]
|
|
28
|
+
webauto ui cli status [--json]
|
|
29
|
+
webauto ui cli snapshot [--json]
|
|
30
|
+
webauto ui cli tab --tab <id|label>
|
|
31
|
+
webauto ui cli click --selector <css>
|
|
32
|
+
webauto ui cli focus --selector <css>
|
|
33
|
+
webauto ui cli input --selector <css> --value <text>
|
|
34
|
+
webauto ui cli select --selector <css> --value <value>
|
|
35
|
+
webauto ui cli press --key <Enter|Escape|...> [--selector <css>]
|
|
36
|
+
webauto ui cli probe [--selector <css>] [--text <contains>] [--exact] [--detailed]
|
|
37
|
+
webauto ui cli click-text --text <button_text> [--selector "button"] [--nth 0]
|
|
38
|
+
webauto ui cli dialogs --value silent|restore
|
|
39
|
+
webauto ui cli wait --selector <css> [--state visible|exists|hidden|text_contains|text_equals|value_equals|not_disabled] [--value <text>] [--timeout 15000] [--interval 250]
|
|
40
|
+
webauto ui cli full-cover [--build] [--install] [--output <report.json>] [--keep-open]
|
|
41
|
+
webauto ui cli run --file <steps.json> [--continue-on-error]
|
|
42
|
+
webauto ui cli stop
|
|
43
|
+
|
|
44
|
+
Options:
|
|
45
|
+
--host <host> UI CLI bridge host (default 127.0.0.1)
|
|
46
|
+
--port <n> UI CLI bridge port (default 7716)
|
|
47
|
+
--auto-start 未检测到 UI 时自动拉起
|
|
48
|
+
--keep-open full-cover 完成后不自动关闭 UI
|
|
49
|
+
--json 输出 JSON
|
|
50
|
+
|
|
51
|
+
Steps JSON format:
|
|
52
|
+
{
|
|
53
|
+
"steps": [
|
|
54
|
+
{ "action": "tab", "tabId": "tasks" },
|
|
55
|
+
{ "action": "input", "selector": "#task-keyword", "value": "春晚" },
|
|
56
|
+
{ "action": "click", "selector": "#task-run-btn" },
|
|
57
|
+
{ "action": "wait", "selector": "#run-id-text", "state": "exists", "timeoutMs": 20000 }
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseIntSafe(input, fallback) {
|
|
64
|
+
const n = Number(input);
|
|
65
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function readControlFile() {
|
|
69
|
+
try {
|
|
70
|
+
if (!existsSync(CONTROL_FILE)) return null;
|
|
71
|
+
const raw = JSON.parse(readFileSync(CONTROL_FILE, 'utf8'));
|
|
72
|
+
const host = String(raw?.host || '').trim() || DEFAULT_HOST;
|
|
73
|
+
const port = parseIntSafe(raw?.port, DEFAULT_PORT);
|
|
74
|
+
return { host, port };
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveEndpoint() {
|
|
81
|
+
const fromFile = readControlFile();
|
|
82
|
+
const host = String(args.host || fromFile?.host || DEFAULT_HOST).trim();
|
|
83
|
+
const port = parseIntSafe(args.port || fromFile?.port, DEFAULT_PORT);
|
|
84
|
+
return { host, port };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function requestJson(endpoint, pathname, init = {}) {
|
|
88
|
+
const url = `http://${endpoint.host}:${endpoint.port}${pathname}`;
|
|
89
|
+
const res = await fetch(url, init);
|
|
90
|
+
const json = await res.json().catch(() => ({}));
|
|
91
|
+
return { ok: res.ok, status: res.status, json };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function sleep(ms) {
|
|
95
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function waitForHealth(endpoint, timeoutMs = 30_000) {
|
|
99
|
+
const started = Date.now();
|
|
100
|
+
while (Date.now() - started <= timeoutMs) {
|
|
101
|
+
try {
|
|
102
|
+
const ret = await requestJson(endpoint, '/health');
|
|
103
|
+
if (ret.ok && ret.json?.ok) return ret.json;
|
|
104
|
+
} catch {
|
|
105
|
+
// keep polling
|
|
106
|
+
}
|
|
107
|
+
await sleep(300);
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function startConsoleIfNeeded(endpoint) {
|
|
113
|
+
const health = await waitForHealth(endpoint, 1500);
|
|
114
|
+
if (health) return health;
|
|
115
|
+
|
|
116
|
+
const uiConsoleScript = path.join(APP_ROOT, 'entry', 'ui-console.mjs');
|
|
117
|
+
const runUiConsole = async (extraArgs = []) => {
|
|
118
|
+
await new Promise((resolve, reject) => {
|
|
119
|
+
const child = spawn(process.execPath, [uiConsoleScript, ...extraArgs], {
|
|
120
|
+
cwd: ROOT,
|
|
121
|
+
env: process.env,
|
|
122
|
+
stdio: 'inherit',
|
|
123
|
+
});
|
|
124
|
+
child.on('error', reject);
|
|
125
|
+
child.on('exit', (code) => {
|
|
126
|
+
if (code === 0) resolve();
|
|
127
|
+
else reject(new Error(`ui console command failed with code=${code}: ${extraArgs.join(' ') || 'start'}`));
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
if (args.install) await runUiConsole(['--install']);
|
|
133
|
+
if (args.build) await runUiConsole(['--build']);
|
|
134
|
+
await runUiConsole([]);
|
|
135
|
+
|
|
136
|
+
const ready = await waitForHealth(endpoint, 45_000);
|
|
137
|
+
if (!ready) throw new Error('ui cli bridge is not ready after start');
|
|
138
|
+
return ready;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function printOutput(payload) {
|
|
142
|
+
if (args.json) {
|
|
143
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (typeof payload === 'string') {
|
|
147
|
+
console.log(payload);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function sendAction(endpoint, payload) {
|
|
154
|
+
return requestJson(endpoint, '/action', {
|
|
155
|
+
method: 'POST',
|
|
156
|
+
headers: { 'Content-Type': 'application/json' },
|
|
157
|
+
body: JSON.stringify(payload),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function runSteps(endpoint, filePath) {
|
|
162
|
+
const abs = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
|
|
163
|
+
if (!existsSync(abs)) throw new Error(`steps file not found: ${abs}`);
|
|
164
|
+
const parsed = JSON.parse(readFileSync(abs, 'utf8'));
|
|
165
|
+
const steps = Array.isArray(parsed?.steps) ? parsed.steps : [];
|
|
166
|
+
if (steps.length === 0) throw new Error('steps is empty');
|
|
167
|
+
|
|
168
|
+
const results = [];
|
|
169
|
+
for (let i = 0; i < steps.length; i += 1) {
|
|
170
|
+
const step = steps[i] || {};
|
|
171
|
+
const action = String(step.action || '').trim();
|
|
172
|
+
if (!action) throw new Error(`step ${i + 1} missing action`);
|
|
173
|
+
|
|
174
|
+
if (action === 'sleep') {
|
|
175
|
+
const ms = parseIntSafe(step.ms, 500);
|
|
176
|
+
await sleep(ms);
|
|
177
|
+
const out = { ok: true, action, ms, index: i + 1 };
|
|
178
|
+
results.push(out);
|
|
179
|
+
if (!args.json) console.log(`[ui-cli] step ${i + 1}/${steps.length} sleep ${ms}ms`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const ret = await sendAction(endpoint, step);
|
|
184
|
+
const out = { index: i + 1, action, ok: ret.ok && Boolean(ret.json?.ok), result: ret.json };
|
|
185
|
+
results.push(out);
|
|
186
|
+
if (!out.ok && !args['continue-on-error']) {
|
|
187
|
+
return { ok: false, failedAt: i + 1, results };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return { ok: true, results };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function outputPathOrDefault() {
|
|
194
|
+
const candidate = String(args.output || '').trim();
|
|
195
|
+
if (candidate) return path.isAbsolute(candidate) ? candidate : path.resolve(process.cwd(), candidate);
|
|
196
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
197
|
+
return path.join(process.cwd(), '.tmp', `ui-cli-full-cover-${ts}.json`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function runFullCover(endpoint) {
|
|
201
|
+
const report = {
|
|
202
|
+
ok: true,
|
|
203
|
+
startedAt: new Date().toISOString(),
|
|
204
|
+
finishedAt: '',
|
|
205
|
+
endpoint,
|
|
206
|
+
steps: [],
|
|
207
|
+
controls: {
|
|
208
|
+
setup: [],
|
|
209
|
+
tasks: [],
|
|
210
|
+
config: [],
|
|
211
|
+
dashboard: [],
|
|
212
|
+
scheduler: [],
|
|
213
|
+
account: [],
|
|
214
|
+
logs: [],
|
|
215
|
+
settings: [],
|
|
216
|
+
},
|
|
217
|
+
errors: [],
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const pushStep = (name, ok, detail = {}) => {
|
|
221
|
+
report.steps.push({
|
|
222
|
+
ts: new Date().toISOString(),
|
|
223
|
+
name,
|
|
224
|
+
ok: Boolean(ok),
|
|
225
|
+
...detail,
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const runAction = async (name, payload, options = {}) => {
|
|
230
|
+
const ret = await sendAction(endpoint, payload);
|
|
231
|
+
const ok = ret.ok && Boolean(ret.json?.ok);
|
|
232
|
+
pushStep(name, ok, { payload, result: ret.json });
|
|
233
|
+
if (!ok && options.optional !== true) {
|
|
234
|
+
const err = new Error(ret.json?.error || `action_failed:${name}`);
|
|
235
|
+
err.result = ret.json;
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
return ret.json;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const probeRaw = async (selector, extra = {}) => {
|
|
242
|
+
const ret = await sendAction(endpoint, { action: 'probe', selector, ...extra });
|
|
243
|
+
return ret.json || {};
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const snapshotRaw = async () => {
|
|
247
|
+
const ret = await sendAction(endpoint, { action: 'snapshot' });
|
|
248
|
+
return ret.json || {};
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const waitForElement = async (selector, attempts = 40, intervalMs = 500) => {
|
|
252
|
+
for (let i = 0; i < attempts; i += 1) {
|
|
253
|
+
const raw = await probeRaw(selector);
|
|
254
|
+
const exists = Boolean(raw?.exists || (raw?.count || 0) > 0);
|
|
255
|
+
if (exists) return true;
|
|
256
|
+
await sleep(intervalMs);
|
|
257
|
+
}
|
|
258
|
+
throw new Error(`wait_timeout:${selector}`);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const ensureTabActive = async (label, id) => {
|
|
262
|
+
for (let i = 0; i < 4; i += 1) {
|
|
263
|
+
await tab(label);
|
|
264
|
+
const snap = await snapshotRaw();
|
|
265
|
+
const activeId = String(snap?.snapshot?.activeTabId || snap?.activeTabId || '').trim();
|
|
266
|
+
if (!id || activeId === id) return true;
|
|
267
|
+
await sleep(300);
|
|
268
|
+
}
|
|
269
|
+
return false;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const runProbe = async (bucket, selector, extra = {}, options = {}) => {
|
|
273
|
+
if (!report.controls[bucket]) {
|
|
274
|
+
console.error('runProbe: invalid bucket', bucket, 'available:', Object.keys(report.controls));
|
|
275
|
+
}
|
|
276
|
+
const probe = await runAction(`probe:${selector || 'body'}`, { action: 'probe', selector, ...extra }, { optional: options.optional === true });
|
|
277
|
+
const hasText = typeof extra?.text === 'string' && extra.text.trim().length > 0;
|
|
278
|
+
const exists = Boolean(probe?.exists || (probe?.count || 0) > 0);
|
|
279
|
+
const textMatched = hasText ? Number(probe?.textMatchedCount || 0) > 0 : true;
|
|
280
|
+
const ok = exists && textMatched;
|
|
281
|
+
if (report.controls[bucket]) {
|
|
282
|
+
report.controls[bucket].push({
|
|
283
|
+
selector: selector || 'body',
|
|
284
|
+
text: hasText ? String(extra.text).trim() : '',
|
|
285
|
+
ok,
|
|
286
|
+
probe,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (!ok && options.optional !== true) {
|
|
290
|
+
throw new Error(`probe_failed:${bucket}:${selector || 'body'}${hasText ? ':text_not_matched' : ''}`);
|
|
291
|
+
}
|
|
292
|
+
return probe;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const tab = async (label) => runAction(`tab:${label}`, { action: 'tab', tabLabel: label });
|
|
296
|
+
const click = async (selector, optional = false) => runAction(`click:${selector}`, { action: 'click', selector }, { optional });
|
|
297
|
+
const input = async (selector, value) => runAction(`input:${selector}`, { action: 'input', selector, value });
|
|
298
|
+
const select = async (selector, value) => runAction(`select:${selector}`, { action: 'select', selector, value });
|
|
299
|
+
const wait = async (selector, timeoutMs = 15000, state = 'visible') =>
|
|
300
|
+
runAction(`wait:${selector}`, { action: 'wait', selector, timeoutMs, state });
|
|
301
|
+
const clickText = async (text, selector = 'button', optional = false) =>
|
|
302
|
+
runAction(`click_text:${text}`, { action: 'click_text', selector, text }, { optional });
|
|
303
|
+
|
|
304
|
+
const taskName = `ui-cli-full-${Date.now()}`;
|
|
305
|
+
const keywordSeed = taskName;
|
|
306
|
+
try {
|
|
307
|
+
await runAction('wait:tabs_ready', { action: 'wait', selector: '#tabs .tab', state: 'exists', timeoutMs: 20000 });
|
|
308
|
+
await runAction('dialogs:silent', { action: 'dialogs', value: 'silent' });
|
|
309
|
+
|
|
310
|
+
await tab('初始化');
|
|
311
|
+
await wait('#env-check-btn');
|
|
312
|
+
await click('#env-check-btn');
|
|
313
|
+
await runProbe('setup', '#env-camo');
|
|
314
|
+
await runProbe('setup', '#env-unified');
|
|
315
|
+
await runProbe('setup', '#env-browser');
|
|
316
|
+
await runProbe('setup', '#env-firefox');
|
|
317
|
+
await runProbe('setup', '#env-geoip');
|
|
318
|
+
await runProbe('setup', '#repair-camo-btn');
|
|
319
|
+
await runProbe('setup', '#repair-core-btn');
|
|
320
|
+
await runProbe('setup', '#repair-core2-btn');
|
|
321
|
+
await runProbe('setup', '#repair-runtime-btn');
|
|
322
|
+
await runProbe('setup', '#repair-geoip-btn');
|
|
323
|
+
await runProbe('setup', '#env-check-btn');
|
|
324
|
+
await runProbe('setup', '#env-repair-all-btn');
|
|
325
|
+
await runProbe('setup', '#env-repair-history');
|
|
326
|
+
await runProbe('setup', '#account-list');
|
|
327
|
+
await runProbe('setup', '#new-alias-input');
|
|
328
|
+
await runProbe('setup', '#add-account-btn');
|
|
329
|
+
await runProbe('setup', '#setup-status-text');
|
|
330
|
+
await runProbe('setup', '#enter-main-btn');
|
|
331
|
+
|
|
332
|
+
await tab('任务'); await wait('#task-keyword');
|
|
333
|
+
await runProbe('tasks', '#task-keyword');
|
|
334
|
+
await runProbe('tasks', '#task-target');
|
|
335
|
+
await runProbe('tasks', '#task-env');
|
|
336
|
+
await runProbe('tasks', '#task-profile');
|
|
337
|
+
await runProbe('tasks', '#task-platform');
|
|
338
|
+
await runProbe('tasks', '#task-save-btn');
|
|
339
|
+
await runProbe('tasks', '#task-run-btn');
|
|
340
|
+
await runProbe('tasks', '#task-body');
|
|
341
|
+
await runProbe('tasks', '#task-comments');
|
|
342
|
+
await runProbe('tasks', '#task-target');
|
|
343
|
+
await runProbe('tasks', '#task-likes');
|
|
344
|
+
await runProbe('tasks', '#task-like-keywords');
|
|
345
|
+
await runProbe('tasks', '#task-target');
|
|
346
|
+
await runProbe('tasks', '#task-env');
|
|
347
|
+
await runProbe('tasks', '#task-comments');
|
|
348
|
+
await runProbe('tasks', '#task-run-btn');
|
|
349
|
+
await input('#task-keyword', 'ui-cli-full-cover');
|
|
350
|
+
await input('#task-target', '100');
|
|
351
|
+
await select('#task-platform', 'last');
|
|
352
|
+
await select('#task-platform', 'preset1');
|
|
353
|
+
await select('#task-platform', 'last');
|
|
354
|
+
await select('#task-env', 'debug');
|
|
355
|
+
await select('#task-env', 'prod');
|
|
356
|
+
await click('#task-body');
|
|
357
|
+
await click('#task-body');
|
|
358
|
+
await click('#task-comments');
|
|
359
|
+
await click('#task-comments');
|
|
360
|
+
await input('#task-target', '150');
|
|
361
|
+
await click('#task-likes');
|
|
362
|
+
await input('#task-like-keywords', '真牛逼,购买链接');
|
|
363
|
+
await input('#task-target', '8');
|
|
364
|
+
await click('#task-env');
|
|
365
|
+
await click('#task-comments');
|
|
366
|
+
await click('#task-run-btn');
|
|
367
|
+
|
|
368
|
+
await tab('看板');
|
|
369
|
+
await wait('#toggle-logs-btn');
|
|
370
|
+
await runProbe('dashboard', '#stat-collected');
|
|
371
|
+
await runProbe('dashboard', '#stat-success');
|
|
372
|
+
await runProbe('dashboard', '#stat-failed');
|
|
373
|
+
await runProbe('dashboard', '#stat-remaining');
|
|
374
|
+
await runProbe('dashboard', '#task-keyword');
|
|
375
|
+
await runProbe('dashboard', '#task-target');
|
|
376
|
+
await runProbe('dashboard', '#task-account');
|
|
377
|
+
await runProbe('dashboard', '#current-phase');
|
|
378
|
+
await runProbe('dashboard', '#current-action');
|
|
379
|
+
await runProbe('dashboard', '#progress-percent');
|
|
380
|
+
await runProbe('dashboard', '#progress-bar');
|
|
381
|
+
await runProbe('dashboard', '#stat-comments');
|
|
382
|
+
await runProbe('dashboard', '#stat-likes');
|
|
383
|
+
await runProbe('dashboard', '#stat-ratelimit');
|
|
384
|
+
await runProbe('dashboard', '#stat-elapsed');
|
|
385
|
+
await runProbe('dashboard', '#toggle-logs-btn');
|
|
386
|
+
await runProbe('dashboard', '#pause-btn');
|
|
387
|
+
await runProbe('dashboard', '#stop-btn');
|
|
388
|
+
await runProbe('dashboard', '#run-id-text');
|
|
389
|
+
await runProbe('dashboard', '#error-count-text');
|
|
390
|
+
await runProbe('dashboard', '#recent-errors-empty');
|
|
391
|
+
await runProbe('dashboard', '#recent-errors-list');
|
|
392
|
+
await runProbe('dashboard', '#logs-container');
|
|
393
|
+
await click('#toggle-logs-btn');
|
|
394
|
+
await click('#pause-btn');
|
|
395
|
+
await click('#pause-btn');
|
|
396
|
+
await click('#stop-btn', true);
|
|
397
|
+
|
|
398
|
+
await tab('定时任务');
|
|
399
|
+
await wait('#scheduler-name');
|
|
400
|
+
await runProbe('scheduler', '#scheduler-refresh-btn');
|
|
401
|
+
await runProbe('scheduler', '#scheduler-run-due-btn');
|
|
402
|
+
await runProbe('scheduler', '#scheduler-export-all-btn');
|
|
403
|
+
await runProbe('scheduler', '#scheduler-import-btn');
|
|
404
|
+
await runProbe('scheduler', '#scheduler-daemon-interval');
|
|
405
|
+
await runProbe('scheduler', '#scheduler-daemon-start-btn');
|
|
406
|
+
await runProbe('scheduler', '#scheduler-daemon-stop-btn');
|
|
407
|
+
await runProbe('scheduler', '#scheduler-daemon-status');
|
|
408
|
+
await runProbe('scheduler', '#scheduler-editing-id');
|
|
409
|
+
await runProbe('scheduler', '#scheduler-name');
|
|
410
|
+
await runProbe('scheduler', '#scheduler-enabled');
|
|
411
|
+
await runProbe('scheduler', '#scheduler-type');
|
|
412
|
+
await runProbe('scheduler', '#scheduler-interval-wrap');
|
|
413
|
+
await runProbe('scheduler', '#scheduler-runat-wrap');
|
|
414
|
+
await runProbe('scheduler', '#scheduler-interval');
|
|
415
|
+
await runProbe('scheduler', '#scheduler-runat');
|
|
416
|
+
await runProbe('scheduler', '#scheduler-max-runs');
|
|
417
|
+
await runProbe('scheduler', '#scheduler-profile');
|
|
418
|
+
await runProbe('scheduler', '#scheduler-keyword');
|
|
419
|
+
await runProbe('scheduler', '#scheduler-max-notes');
|
|
420
|
+
await runProbe('scheduler', '#scheduler-env');
|
|
421
|
+
await runProbe('scheduler', '#scheduler-comments');
|
|
422
|
+
await runProbe('scheduler', '#scheduler-likes');
|
|
423
|
+
await runProbe('scheduler', '#scheduler-headless');
|
|
424
|
+
await runProbe('scheduler', '#scheduler-dryrun');
|
|
425
|
+
await runProbe('scheduler', '#scheduler-like-keywords');
|
|
426
|
+
await runProbe('scheduler', '#scheduler-save-btn');
|
|
427
|
+
await runProbe('scheduler', '#scheduler-reset-btn');
|
|
428
|
+
await select('#scheduler-type', 'once');
|
|
429
|
+
await wait('#scheduler-runat-wrap', 8000, 'visible');
|
|
430
|
+
await wait('#scheduler-interval-wrap', 8000, 'hidden');
|
|
431
|
+
await select('#scheduler-type', 'daily');
|
|
432
|
+
await wait('#scheduler-runat-wrap', 8000, 'visible');
|
|
433
|
+
await select('#scheduler-type', 'weekly');
|
|
434
|
+
await wait('#scheduler-runat-wrap', 8000, 'visible');
|
|
435
|
+
await select('#scheduler-type', 'interval');
|
|
436
|
+
await wait('#scheduler-interval-wrap', 8000, 'visible');
|
|
437
|
+
await wait('#scheduler-runat-wrap', 8000, 'hidden');
|
|
438
|
+
await input('#scheduler-name', taskName);
|
|
439
|
+
await select('#scheduler-type', 'interval');
|
|
440
|
+
await input('#scheduler-interval', '20');
|
|
441
|
+
await input('#scheduler-profile', 'xiaohongshu-batch-0');
|
|
442
|
+
await input('#scheduler-keyword', keywordSeed);
|
|
443
|
+
await input('#scheduler-max-notes', '20');
|
|
444
|
+
await select('#scheduler-env', 'debug');
|
|
445
|
+
await click('#scheduler-comments');
|
|
446
|
+
await click('#scheduler-comments');
|
|
447
|
+
await click('#scheduler-likes');
|
|
448
|
+
await click('#scheduler-headless');
|
|
449
|
+
await click('#scheduler-dryrun');
|
|
450
|
+
await input('#scheduler-like-keywords', '真牛逼,购买链接');
|
|
451
|
+
await click('#scheduler-save-btn');
|
|
452
|
+
await wait('#scheduler-list');
|
|
453
|
+
// Wait for async schedule refresh to render the new task.
|
|
454
|
+
for (let i = 0; i < 6; i += 1) {
|
|
455
|
+
const raw = await probeRaw('#scheduler-list', { text: taskName });
|
|
456
|
+
if (Number(raw?.textMatchedCount || 0) > 0) break;
|
|
457
|
+
await sleep(500);
|
|
458
|
+
}
|
|
459
|
+
await runProbe('scheduler', '#scheduler-list', { text: taskName });
|
|
460
|
+
await runProbe('scheduler', '#scheduler-list button', { text: '编辑' });
|
|
461
|
+
await runProbe('scheduler', '#scheduler-list button', { text: '执行' });
|
|
462
|
+
await runProbe('scheduler', '#scheduler-list button', { text: '导出' });
|
|
463
|
+
await runProbe('scheduler', '#scheduler-list button', { text: '删除' });
|
|
464
|
+
await clickText('编辑', '#scheduler-list button', true);
|
|
465
|
+
await click('#scheduler-refresh-btn');
|
|
466
|
+
await input('#scheduler-daemon-interval', '7');
|
|
467
|
+
await click('#scheduler-daemon-start-btn');
|
|
468
|
+
await wait('#scheduler-daemon-status', 10000, 'exists');
|
|
469
|
+
await click('#scheduler-daemon-stop-btn');
|
|
470
|
+
await click('#scheduler-reset-btn');
|
|
471
|
+
|
|
472
|
+
const tabOk = await ensureTabActive('账户管理', 'account-manager');
|
|
473
|
+
if (!tabOk) throw new Error('tab_failed:账户管理');
|
|
474
|
+
await waitForElement('#recheck-env-btn', 40, 500);
|
|
475
|
+
await waitForElement('#add-account-btn', 20, 500);
|
|
476
|
+
await waitForElement('#check-all-btn', 20, 500);
|
|
477
|
+
await waitForElement('#refresh-expired-btn', 20, 500);
|
|
478
|
+
await runProbe('account', '#env-camo');
|
|
479
|
+
await runProbe('account', '#env-unified');
|
|
480
|
+
await runProbe('account', '#env-browser');
|
|
481
|
+
await runProbe('account', '#env-firefox');
|
|
482
|
+
await runProbe('account', '#recheck-env-btn');
|
|
483
|
+
await runProbe('account', '#account-list');
|
|
484
|
+
await runProbe('account', '#new-account-alias-input');
|
|
485
|
+
await runProbe('account', '#add-account-btn');
|
|
486
|
+
await runProbe('account', '#add-account-confirm-btn');
|
|
487
|
+
await runProbe('account', '#check-all-btn');
|
|
488
|
+
await runProbe('account', '#refresh-expired-btn');
|
|
489
|
+
await input('#new-account-alias-input', 'full-cover');
|
|
490
|
+
await click('#recheck-env-btn', true);
|
|
491
|
+
await click('#check-all-btn', true);
|
|
492
|
+
|
|
493
|
+
await tab('日志');
|
|
494
|
+
await wait('#logs-active-only');
|
|
495
|
+
await runProbe('logs', '#logs-active-only');
|
|
496
|
+
await runProbe('logs', '#logs-show-global');
|
|
497
|
+
await runProbe('logs', 'button', { text: '清空日志' });
|
|
498
|
+
await runProbe('logs', 'button', { text: '复制公共日志' }, { optional: true });
|
|
499
|
+
await runProbe('logs', 'button', { text: '复制分片日志' }, { optional: true });
|
|
500
|
+
await click('#logs-active-only');
|
|
501
|
+
await click('#logs-show-global');
|
|
502
|
+
await clickText('清空日志', 'button');
|
|
503
|
+
await clickText('复制公共日志', 'button', true);
|
|
504
|
+
await clickText('复制分片日志', 'button', true);
|
|
505
|
+
|
|
506
|
+
await tab('设置');
|
|
507
|
+
await runProbe('settings', 'body', { text: 'AI 智能回复' });
|
|
508
|
+
await runProbe('settings', 'body', { text: 'Core Daemon' });
|
|
509
|
+
await runProbe('settings', 'body', { text: 'downloadRoot' });
|
|
510
|
+
await runProbe('settings', 'body', { text: 'defaultEnv' });
|
|
511
|
+
await runProbe('settings', 'body', { text: 'defaultKeyword' });
|
|
512
|
+
await runProbe('settings', 'body', { text: 'loginTimeoutSec' });
|
|
513
|
+
await runProbe('settings', 'body', { text: 'cmdTimeoutSec' });
|
|
514
|
+
await runProbe('settings', 'body', { text: 'API Base URL' });
|
|
515
|
+
await runProbe('settings', 'body', { text: 'API Key' });
|
|
516
|
+
await runProbe('settings', 'body', { text: '模型' });
|
|
517
|
+
await runProbe('settings', 'body', { text: '获取模型列表' });
|
|
518
|
+
await runProbe('settings', 'body', { text: '测试连通' });
|
|
519
|
+
await runProbe('settings', 'body', { text: 'Temperature' });
|
|
520
|
+
await runProbe('settings', 'body', { text: '最大字数' });
|
|
521
|
+
await runProbe('settings', 'body', { text: '超时(ms)' });
|
|
522
|
+
await runProbe('settings', 'body', { text: '回复风格' });
|
|
523
|
+
await runProbe('settings', 'body', { text: '自定义风格' });
|
|
524
|
+
await runProbe('settings', 'body', { text: '调试(已并入设置)' });
|
|
525
|
+
await clickText('保存', 'button');
|
|
526
|
+
|
|
527
|
+
await runAction('dialogs:restore', { action: 'dialogs', value: 'restore' }, { optional: true });
|
|
528
|
+
} catch (err) {
|
|
529
|
+
report.ok = false;
|
|
530
|
+
report.errors.push({
|
|
531
|
+
message: err?.message || String(err),
|
|
532
|
+
result: err?.result || null,
|
|
533
|
+
});
|
|
534
|
+
} finally {
|
|
535
|
+
const coverage = {};
|
|
536
|
+
let total = 0;
|
|
537
|
+
let passed = 0;
|
|
538
|
+
for (const [bucket, items] of Object.entries(report.controls)) {
|
|
539
|
+
const rows = Array.isArray(items) ? items : [];
|
|
540
|
+
const bucketPassed = rows.filter((x) => x?.ok).length;
|
|
541
|
+
coverage[bucket] = {
|
|
542
|
+
total: rows.length,
|
|
543
|
+
passed: bucketPassed,
|
|
544
|
+
failed: Math.max(0, rows.length - bucketPassed),
|
|
545
|
+
};
|
|
546
|
+
total += rows.length;
|
|
547
|
+
passed += bucketPassed;
|
|
548
|
+
}
|
|
549
|
+
report.coverage = {
|
|
550
|
+
total,
|
|
551
|
+
passed,
|
|
552
|
+
failed: Math.max(0, total - passed),
|
|
553
|
+
buckets: coverage,
|
|
554
|
+
};
|
|
555
|
+
report.finishedAt = new Date().toISOString();
|
|
556
|
+
const output = outputPathOrDefault();
|
|
557
|
+
mkdirSync(path.dirname(output), { recursive: true });
|
|
558
|
+
writeFileSync(output, JSON.stringify(report, null, 2));
|
|
559
|
+
report.output = output;
|
|
560
|
+
if (!args['keep-open']) {
|
|
561
|
+
await sendAction(endpoint, { action: 'close_window' }).catch(() => null);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return report;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async function main() {
|
|
569
|
+
const cmd = String(args._[0] || '').trim();
|
|
570
|
+
if (args.help || !cmd) {
|
|
571
|
+
printHelp();
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const endpoint = resolveEndpoint();
|
|
576
|
+
const needStart = args['auto-start'] || cmd === 'start' || cmd === 'full-cover';
|
|
577
|
+
if (needStart) {
|
|
578
|
+
await startConsoleIfNeeded(endpoint);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (cmd === 'start') {
|
|
582
|
+
const status = await waitForHealth(endpoint, 1000);
|
|
583
|
+
if (!status) throw new Error('ui cli bridge not healthy');
|
|
584
|
+
printOutput({ ok: true, endpoint, status });
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (cmd === 'status' || cmd === 'snapshot') {
|
|
589
|
+
const pathName = cmd === 'snapshot' ? '/snapshot' : '/status';
|
|
590
|
+
const ret = await requestJson(endpoint, pathName);
|
|
591
|
+
if (!ret.ok) throw new Error(ret.json?.error || `http_${ret.status}`);
|
|
592
|
+
printOutput(ret.json);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (cmd === 'stop') {
|
|
597
|
+
const ret = await sendAction(endpoint, { action: 'close_window' });
|
|
598
|
+
if (!ret.ok) throw new Error(ret.json?.error || `http_${ret.status}`);
|
|
599
|
+
printOutput(ret.json);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (cmd === 'run') {
|
|
604
|
+
const filePath = String(args.file || '').trim();
|
|
605
|
+
if (!filePath) throw new Error('missing --file');
|
|
606
|
+
const result = await runSteps(endpoint, filePath);
|
|
607
|
+
printOutput(result);
|
|
608
|
+
if (!result.ok) process.exit(1);
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (cmd === 'full-cover') {
|
|
613
|
+
const report = await runFullCover(endpoint);
|
|
614
|
+
printOutput(report);
|
|
615
|
+
if (!report.ok) process.exit(1);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const actionMap = new Set(['tab', 'click', 'focus', 'input', 'select', 'press', 'wait', 'probe', 'click-text', 'dialogs']);
|
|
620
|
+
if (!actionMap.has(cmd)) {
|
|
621
|
+
printHelp();
|
|
622
|
+
process.exit(2);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const payload = { action: cmd };
|
|
626
|
+
if (cmd === 'tab') {
|
|
627
|
+
const tabValue = String(args.tab || '').trim();
|
|
628
|
+
if (tabValue) {
|
|
629
|
+
payload.tabId = tabValue;
|
|
630
|
+
payload.tabLabel = tabValue;
|
|
631
|
+
} else {
|
|
632
|
+
payload.tabLabel = String(args.label || '').trim();
|
|
633
|
+
}
|
|
634
|
+
if (!payload.tabId && !payload.tabLabel) throw new Error('tab requires --tab or --label');
|
|
635
|
+
} else {
|
|
636
|
+
if (cmd === 'click-text') payload.action = 'click_text';
|
|
637
|
+
if (args.selector) payload.selector = String(args.selector);
|
|
638
|
+
if (args.value != null) payload.value = String(args.value);
|
|
639
|
+
if (args.text != null) payload.text = String(args.text);
|
|
640
|
+
if (args.key != null) payload.key = String(args.key);
|
|
641
|
+
if (args.state != null) payload.state = String(args.state);
|
|
642
|
+
if (args.nth != null) payload.nth = parseIntSafe(args.nth, 0);
|
|
643
|
+
if (args.exact === true) payload.exact = true;
|
|
644
|
+
if (args.timeout != null) payload.timeoutMs = parseIntSafe(args.timeout, 15000);
|
|
645
|
+
if (args.interval != null) payload.intervalMs = parseIntSafe(args.interval, 250);
|
|
646
|
+
if (args.detailed === true) payload.detailed = true;
|
|
647
|
+
if (cmd === 'dialogs' && !payload.value) {
|
|
648
|
+
throw new Error('dialogs requires --value silent|restore');
|
|
649
|
+
}
|
|
650
|
+
if (cmd === 'click-text' && !payload.text && !payload.value) {
|
|
651
|
+
throw new Error('click-text requires --text');
|
|
652
|
+
}
|
|
653
|
+
if (cmd !== 'press' && cmd !== 'probe' && cmd !== 'click-text' && cmd !== 'dialogs' && !payload.selector && cmd !== 'wait') {
|
|
654
|
+
throw new Error(`${cmd} requires --selector`);
|
|
655
|
+
}
|
|
656
|
+
if (cmd === 'wait' && !payload.selector) {
|
|
657
|
+
throw new Error('wait requires --selector');
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const ret = await sendAction(endpoint, payload);
|
|
662
|
+
if (!ret.ok || !ret.json?.ok) {
|
|
663
|
+
printOutput(ret.json || { ok: false, error: `http_${ret.status}` });
|
|
664
|
+
process.exit(1);
|
|
665
|
+
}
|
|
666
|
+
printOutput(ret.json);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
main().catch((err) => {
|
|
670
|
+
console.error(`[ui-cli] ${err?.message || String(err)}`);
|
|
671
|
+
process.exit(1);
|
|
672
|
+
});
|