@pyrokine/mcp-chrome 2.3.2 → 2.4.0
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 +19 -8
- package/README_zh.md +16 -5
- package/dist/anti-detection/behavior.d.ts.map +1 -1
- package/dist/anti-detection/behavior.js.map +1 -1
- package/dist/anti-detection/injection.js.map +1 -1
- package/dist/cdp/client.d.ts.map +1 -1
- package/dist/cdp/client.js.map +1 -1
- package/dist/cdp/launcher.d.ts.map +1 -1
- package/dist/cdp/launcher.js.map +1 -1
- package/dist/core/auto-wait.d.ts.map +1 -1
- package/dist/core/auto-wait.js.map +1 -1
- package/dist/core/browser-driver.d.ts.map +1 -1
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js.map +1 -1
- package/dist/core/extension-errors.d.ts.map +1 -1
- package/dist/core/locator.d.ts.map +1 -1
- package/dist/core/locator.js.map +1 -1
- package/dist/core/retry.js.map +1 -1
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js.map +1 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/unified-session.d.ts +7 -0
- package/dist/core/unified-session.d.ts.map +1 -1
- package/dist/core/unified-session.js +14 -0
- package/dist/core/unified-session.js.map +1 -1
- package/dist/extension/bridge.d.ts.map +1 -1
- package/dist/extension/bridge.js.map +1 -1
- package/dist/extension/http-server.d.ts.map +1 -1
- package/dist/extension/http-server.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/browse.d.ts.map +1 -1
- package/dist/tools/browse.js +12 -37
- package/dist/tools/browse.js.map +1 -1
- package/dist/tools/cookies.js.map +1 -1
- package/dist/tools/diagnostics.d.ts +31 -0
- package/dist/tools/diagnostics.d.ts.map +1 -0
- package/dist/tools/diagnostics.js +92 -0
- package/dist/tools/diagnostics.js.map +1 -0
- package/dist/tools/evaluate.d.ts +7 -0
- package/dist/tools/evaluate.d.ts.map +1 -1
- package/dist/tools/evaluate.js +111 -85
- package/dist/tools/evaluate.js.map +1 -1
- package/dist/tools/extract.d.ts.map +1 -1
- package/dist/tools/extract.js +26 -8
- package/dist/tools/extract.js.map +1 -1
- package/dist/tools/input.d.ts +2 -0
- package/dist/tools/input.d.ts.map +1 -1
- package/dist/tools/input.js +301 -183
- package/dist/tools/input.js.map +1 -1
- package/dist/tools/logs.d.ts +14 -0
- package/dist/tools/logs.d.ts.map +1 -1
- package/dist/tools/logs.js +37 -9
- package/dist/tools/logs.js.map +1 -1
- package/dist/tools/post-condition.d.ts +18 -1
- package/dist/tools/post-condition.d.ts.map +1 -1
- package/dist/tools/post-condition.js +48 -94
- package/dist/tools/post-condition.js.map +1 -1
- package/dist/tools/target-diagnostics.d.ts +31 -0
- package/dist/tools/target-diagnostics.d.ts.map +1 -0
- package/dist/tools/target-diagnostics.js +85 -0
- package/dist/tools/target-diagnostics.js.map +1 -0
- package/dist/tools/wait.d.ts.map +1 -1
- package/dist/tools/wait.js +182 -208
- package/dist/tools/wait.js.map +1 -1
- package/extension/dist/assets/{index.ts-CJXGbOcp.js → index.ts-BMmrcSQ6.js} +235 -66
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/service-worker-loader.js +1 -1
- package/package.json +14 -10
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { sanitizeErrorMessage } from '../core/error-sanitizer.js';
|
|
2
|
+
const MAX_CANDIDATES = 10;
|
|
3
|
+
const MAX_LOCATOR_TEXT = 500;
|
|
4
|
+
const MAX_CANDIDATE_TEXT = 160;
|
|
5
|
+
const MAX_DIAGNOSTIC_TIMEOUT_MS = 1000;
|
|
6
|
+
export class TargetTimeoutError extends Error {
|
|
7
|
+
context;
|
|
8
|
+
code = 'TARGET_TIMEOUT';
|
|
9
|
+
suggestion = '请检查 locator、frame 和 nth,或根据候选摘要调整目标;连接不可用时先恢复 Extension';
|
|
10
|
+
constructor(context) {
|
|
11
|
+
super(`等待目标元素超时 (${context.timeout}ms)`);
|
|
12
|
+
this.context = context;
|
|
13
|
+
this.name = 'TargetTimeoutError';
|
|
14
|
+
}
|
|
15
|
+
toJSON() {
|
|
16
|
+
return { error: { code: this.code, message: this.message, suggestion: this.suggestion, context: this.context } };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function targetType(target) {
|
|
20
|
+
return Object.keys(target).find((key) => key !== 'nth') ?? 'unknown';
|
|
21
|
+
}
|
|
22
|
+
function boundedTarget(target) {
|
|
23
|
+
return Object.fromEntries(Object.entries(target).map(([key, value]) => [
|
|
24
|
+
key,
|
|
25
|
+
typeof value === 'string' ? value.slice(0, MAX_LOCATOR_TEXT) : value,
|
|
26
|
+
]));
|
|
27
|
+
}
|
|
28
|
+
export function summarizeTargetCandidates(candidates) {
|
|
29
|
+
return candidates.slice(0, MAX_CANDIDATES).map((candidate) => ({
|
|
30
|
+
...(typeof candidate.tag === 'string' ? { tag: candidate.tag.slice(0, 40) } : {}),
|
|
31
|
+
...(typeof candidate.text === 'string' ? { text: candidate.text.slice(0, MAX_CANDIDATE_TEXT) } : {}),
|
|
32
|
+
...(candidate.rect ? { rect: candidate.rect } : {}),
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
async function collectPageCandidates(unifiedSession, timeout) {
|
|
36
|
+
try {
|
|
37
|
+
const candidates = await unifiedSession.evaluate(`(() => Array.from(document.querySelectorAll('input, textarea, select, button, a, [role], [contenteditable="true"]'))
|
|
38
|
+
.slice(0, ${MAX_CANDIDATES})
|
|
39
|
+
.map((el) => {
|
|
40
|
+
const rect = el.getBoundingClientRect();
|
|
41
|
+
return {
|
|
42
|
+
tag: el.tagName.toLowerCase(),
|
|
43
|
+
id: el.id || undefined,
|
|
44
|
+
role: el.getAttribute('role') || undefined,
|
|
45
|
+
label: (el.getAttribute('aria-label') || el.getAttribute('placeholder') || '').slice(0, 80),
|
|
46
|
+
text: (el.textContent || '').replace(/\\s+/g, ' ').trim().slice(0, ${MAX_CANDIDATE_TEXT}),
|
|
47
|
+
visible: rect.width > 0 && rect.height > 0,
|
|
48
|
+
disabled: Boolean(el.disabled) || el.getAttribute('aria-disabled') === 'true',
|
|
49
|
+
bounds: {x: rect.x, y: rect.y, width: rect.width, height: rect.height}
|
|
50
|
+
};
|
|
51
|
+
}))()`, undefined, Math.min(Math.max(timeout ?? MAX_DIAGNOSTIC_TIMEOUT_MS, 1), MAX_DIAGNOSTIC_TIMEOUT_MS));
|
|
52
|
+
return { candidates };
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return {
|
|
56
|
+
candidates: [],
|
|
57
|
+
diagnosticError: sanitizeErrorMessage(error).slice(0, 500),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export async function buildTargetDiagnostics(unifiedSession, target, options) {
|
|
62
|
+
const state = unifiedSession.getState();
|
|
63
|
+
const knownCandidates = options.candidates ? summarizeTargetCandidates(options.candidates) : [];
|
|
64
|
+
const pageCandidates = knownCandidates.length > 0
|
|
65
|
+
? { candidates: knownCandidates }
|
|
66
|
+
: await collectPageCandidates(unifiedSession, options.timeout);
|
|
67
|
+
return {
|
|
68
|
+
locator: boundedTarget(target),
|
|
69
|
+
targetType: targetType(target),
|
|
70
|
+
nth: options.nth,
|
|
71
|
+
currentUrl: state?.url?.slice(0, 500),
|
|
72
|
+
tabId: unifiedSession.getCurrentTargetId(),
|
|
73
|
+
managed: state?.managed ?? false,
|
|
74
|
+
frame: options.frame,
|
|
75
|
+
matchCount: options.matchCount,
|
|
76
|
+
candidates: pageCandidates.candidates,
|
|
77
|
+
lastLocatorState: options.lastState,
|
|
78
|
+
timeout: options.timeout,
|
|
79
|
+
mode: unifiedSession.getMode(),
|
|
80
|
+
connection: unifiedSession.isExtensionConnected() ? 'connected' : 'unavailable',
|
|
81
|
+
...(options.lastError ? { lastError: sanitizeErrorMessage(options.lastError).slice(0, 500) } : {}),
|
|
82
|
+
...(pageCandidates.diagnosticError ? { diagnosticError: pageCandidates.diagnosticError } : {}),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=target-diagnostics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"target-diagnostics.js","sourceRoot":"","sources":["../../src/tools/target-diagnostics.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/wait.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../src/tools/wait.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;
|
|
1
|
+
{"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../../src/tools/wait.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AA4lBxE;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CASxD"}
|
package/dist/tools/wait.js
CHANGED
|
@@ -8,40 +8,19 @@
|
|
|
8
8
|
* - idle: 等待网络空闲
|
|
9
9
|
*/
|
|
10
10
|
import { z } from 'zod';
|
|
11
|
-
import { formatErrorResponse,
|
|
11
|
+
import { formatErrorResponse, getSession, getUnifiedSession, TimeoutError } from '../core/index.js';
|
|
12
12
|
import { DEFAULT_TIMEOUT } from '../core/types.js';
|
|
13
|
+
import { withDiagnosticsResponse } from './diagnostics.js';
|
|
13
14
|
import { targetToFindParams, targetZodSchema } from './schema.js';
|
|
15
|
+
import { buildTargetDiagnostics, TargetTimeoutError } from './target-diagnostics.js';
|
|
14
16
|
/** 轮询间隔(毫秒) */
|
|
15
17
|
const POLL_INTERVAL = 100;
|
|
16
18
|
const NAVIGATION_SNAPSHOT_TIMEOUT = 1000;
|
|
17
|
-
async function
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const network = await unifiedSession.getNetworkRequests();
|
|
22
|
-
return { consoleCount: consoleLogs.length, networkCount: network.length };
|
|
23
|
-
}
|
|
24
|
-
async function captureDiagnosticsDelta(unifiedSession, start) {
|
|
25
|
-
const consoleLogs = await unifiedSession.getConsoleLogs();
|
|
26
|
-
const network = await unifiedSession.getNetworkRequests();
|
|
27
|
-
return {
|
|
28
|
-
console: consoleLogs
|
|
29
|
-
.slice(start.consoleCount)
|
|
30
|
-
.filter((item) => ['error', 'warning', 'warn'].includes(item.level))
|
|
31
|
-
.slice(-20),
|
|
32
|
-
failedRequests: network
|
|
33
|
-
.slice(start.networkCount)
|
|
34
|
-
.filter((item) => item.errorText || (item.status !== undefined && item.status >= 400))
|
|
35
|
-
.slice(-20),
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
async function withDiagnostics(unifiedSession, enabled, action) {
|
|
39
|
-
const start = enabled ? await captureDiagnosticsStart(unifiedSession) : undefined;
|
|
40
|
-
const result = await action();
|
|
41
|
-
if (start) {
|
|
42
|
-
result.diagnostics = await captureDiagnosticsDelta(unifiedSession, start);
|
|
19
|
+
async function sleepWithinBudget(startedAt, timeout, interval) {
|
|
20
|
+
const remaining = timeout - (Date.now() - startedAt);
|
|
21
|
+
if (remaining > 0) {
|
|
22
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(interval, remaining)));
|
|
43
23
|
}
|
|
44
|
-
return result;
|
|
45
24
|
}
|
|
46
25
|
async function getExtensionNavigationSnapshot(unifiedSession, timeout = NAVIGATION_SNAPSHOT_TIMEOUT) {
|
|
47
26
|
const targetId = unifiedSession.getCurrentTargetId();
|
|
@@ -117,9 +96,9 @@ async function handleWait(args) {
|
|
|
117
96
|
};
|
|
118
97
|
}
|
|
119
98
|
const state = args.state ?? 'visible';
|
|
120
|
-
return
|
|
99
|
+
return withDiagnosticsResponse(unifiedSession, args.diagnostics, async () => {
|
|
121
100
|
if (mode === 'extension') {
|
|
122
|
-
await waitForElementExtension(unifiedSession, args.target, state, timeout);
|
|
101
|
+
await waitForElementExtension(unifiedSession, args.target, state, timeout, args.frame);
|
|
123
102
|
}
|
|
124
103
|
else {
|
|
125
104
|
const session = getSession();
|
|
@@ -131,94 +110,86 @@ async function handleWait(args) {
|
|
|
131
110
|
state,
|
|
132
111
|
mode,
|
|
133
112
|
};
|
|
134
|
-
})
|
|
113
|
+
});
|
|
135
114
|
}
|
|
136
115
|
case 'navigation': {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
sawLoading = initial.readyState !== 'complete';
|
|
116
|
+
return withDiagnosticsResponse(unifiedSession, args.diagnostics, async () => {
|
|
117
|
+
if (mode === 'extension') {
|
|
118
|
+
const navStart = Date.now();
|
|
119
|
+
let navCompleted = false;
|
|
120
|
+
let navLastError = null;
|
|
121
|
+
let initialUrl = null;
|
|
122
|
+
let initialTitle = null;
|
|
123
|
+
let sawLoading = false;
|
|
124
|
+
let navUrl = null;
|
|
125
|
+
let navTitle = null;
|
|
126
|
+
try {
|
|
127
|
+
const initial = await getExtensionNavigationSnapshot(unifiedSession, Math.min(timeout, NAVIGATION_SNAPSHOT_TIMEOUT));
|
|
128
|
+
if (unifiedSession.isExtensionConnected()) {
|
|
129
|
+
initialUrl = initial.url;
|
|
130
|
+
initialTitle = initial.title;
|
|
131
|
+
sawLoading = initial.readyState !== 'complete';
|
|
132
|
+
}
|
|
155
133
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
navLastError = err instanceof Error ? err : new Error(String(err));
|
|
160
|
-
}
|
|
161
|
-
while (!navCompleted && Date.now() - navStart < timeout) {
|
|
162
|
-
if (!unifiedSession.isExtensionConnected()) {
|
|
163
|
-
navLastError = new Error('Extension 未连接');
|
|
164
|
-
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
|
|
165
|
-
continue;
|
|
134
|
+
catch (err) {
|
|
135
|
+
sawLoading = true;
|
|
136
|
+
navLastError = err instanceof Error ? err : new Error(String(err));
|
|
166
137
|
}
|
|
167
|
-
|
|
168
|
-
const remaining = timeout - (Date.now() - navStart);
|
|
169
|
-
const pageInfo = await getExtensionNavigationSnapshot(unifiedSession, Math.min(remaining, NAVIGATION_SNAPSHOT_TIMEOUT));
|
|
138
|
+
while (!navCompleted && Date.now() - navStart < timeout) {
|
|
170
139
|
if (!unifiedSession.isExtensionConnected()) {
|
|
171
|
-
navLastError = new Error('Extension
|
|
172
|
-
await
|
|
140
|
+
navLastError = new Error('Extension 未连接');
|
|
141
|
+
await sleepWithinBudget(navStart, timeout, POLL_INTERVAL);
|
|
173
142
|
continue;
|
|
174
143
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
144
|
+
try {
|
|
145
|
+
const remaining = timeout - (Date.now() - navStart);
|
|
146
|
+
const pageInfo = await getExtensionNavigationSnapshot(unifiedSession, Math.min(remaining, NAVIGATION_SNAPSHOT_TIMEOUT));
|
|
147
|
+
if (!unifiedSession.isExtensionConnected()) {
|
|
148
|
+
navLastError = new Error('Extension 在查询导航状态期间断开');
|
|
149
|
+
await sleepWithinBudget(navStart, timeout, POLL_INTERVAL);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (pageInfo.readyState !== 'complete') {
|
|
153
|
+
sawLoading = true;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const changed = (initialUrl !== null && pageInfo.url !== initialUrl) ||
|
|
157
|
+
(initialTitle !== null && pageInfo.title !== initialTitle);
|
|
158
|
+
if (sawLoading || changed || initialUrl === null) {
|
|
159
|
+
navUrl = pageInfo.url;
|
|
160
|
+
navTitle = pageInfo.title;
|
|
161
|
+
navCompleted = true;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
186
164
|
}
|
|
187
165
|
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
navLastError = err instanceof Error ? err : new Error(String(err));
|
|
168
|
+
}
|
|
169
|
+
await sleepWithinBudget(navStart, timeout, POLL_INTERVAL);
|
|
188
170
|
}
|
|
189
|
-
|
|
190
|
-
|
|
171
|
+
if (!navCompleted) {
|
|
172
|
+
const msg = `等待导航完成超时 (${timeout}ms)`;
|
|
173
|
+
throw new TimeoutError(navLastError ? `${msg}: ${navLastError.message}` : msg);
|
|
191
174
|
}
|
|
192
|
-
|
|
175
|
+
return {
|
|
176
|
+
success: true,
|
|
177
|
+
waited: 'navigation',
|
|
178
|
+
url: navUrl,
|
|
179
|
+
title: navTitle,
|
|
180
|
+
mode,
|
|
181
|
+
};
|
|
193
182
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return formatResponse({
|
|
183
|
+
const session = getSession();
|
|
184
|
+
await waitForNavigation(session, timeout);
|
|
185
|
+
const sessionState = session.getState();
|
|
186
|
+
return {
|
|
199
187
|
success: true,
|
|
200
188
|
waited: 'navigation',
|
|
201
|
-
url:
|
|
202
|
-
title:
|
|
189
|
+
url: sessionState?.url,
|
|
190
|
+
title: sessionState?.title,
|
|
203
191
|
mode,
|
|
204
|
-
|
|
205
|
-
? { diagnostics: await captureDiagnosticsDelta(unifiedSession, diagnosticsStart) }
|
|
206
|
-
: {}),
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
// CDP 模式
|
|
210
|
-
const session = getSession();
|
|
211
|
-
await waitForNavigation(session, timeout);
|
|
212
|
-
const sessionState = session.getState();
|
|
213
|
-
return formatResponse({
|
|
214
|
-
success: true,
|
|
215
|
-
waited: 'navigation',
|
|
216
|
-
url: sessionState?.url,
|
|
217
|
-
title: sessionState?.title,
|
|
218
|
-
mode,
|
|
219
|
-
...(diagnosticsStart
|
|
220
|
-
? { diagnostics: await captureDiagnosticsDelta(unifiedSession, diagnosticsStart) }
|
|
221
|
-
: {}),
|
|
192
|
+
};
|
|
222
193
|
});
|
|
223
194
|
}
|
|
224
195
|
case 'time': {
|
|
@@ -238,66 +209,66 @@ async function handleWait(args) {
|
|
|
238
209
|
isError: true,
|
|
239
210
|
};
|
|
240
211
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
212
|
+
return withDiagnosticsResponse(unifiedSession, args.diagnostics, async () => {
|
|
213
|
+
await new Promise((resolve) => setTimeout(resolve, args.ms));
|
|
214
|
+
return {
|
|
215
|
+
success: true,
|
|
216
|
+
waited: 'time',
|
|
217
|
+
ms: args.ms,
|
|
218
|
+
};
|
|
246
219
|
});
|
|
247
220
|
}
|
|
248
221
|
case 'idle': {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// Phase 1: 等待 readyState === 'complete'
|
|
259
|
-
while (Date.now() - idleStart < timeout) {
|
|
260
|
-
if (!unifiedSession.isExtensionConnected()) {
|
|
261
|
-
idleLastError = new Error('Extension 未连接');
|
|
262
|
-
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
try {
|
|
266
|
-
const remaining = timeout - (Date.now() - idleStart);
|
|
267
|
-
const readyState = await unifiedSession.evaluate('document.readyState', undefined, remaining);
|
|
222
|
+
return withDiagnosticsResponse(unifiedSession, args.diagnostics, async () => {
|
|
223
|
+
if (mode === 'extension') {
|
|
224
|
+
// Extension 模式:readyState complete + DOM mutation 静默检测
|
|
225
|
+
const idleStart = Date.now();
|
|
226
|
+
const quietPeriod = args.ms ?? 500;
|
|
227
|
+
let idleCompleted = false;
|
|
228
|
+
let idleLastError = null;
|
|
229
|
+
// Phase 1: 等待 readyState === 'complete'
|
|
230
|
+
while (Date.now() - idleStart < timeout) {
|
|
268
231
|
if (!unifiedSession.isExtensionConnected()) {
|
|
269
|
-
idleLastError = new Error('Extension
|
|
270
|
-
await
|
|
232
|
+
idleLastError = new Error('Extension 未连接');
|
|
233
|
+
await sleepWithinBudget(idleStart, timeout, POLL_INTERVAL);
|
|
271
234
|
continue;
|
|
272
235
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
236
|
+
try {
|
|
237
|
+
const remaining = timeout - (Date.now() - idleStart);
|
|
238
|
+
const readyState = await unifiedSession.evaluate('document.readyState', undefined, remaining);
|
|
239
|
+
if (!unifiedSession.isExtensionConnected()) {
|
|
240
|
+
idleLastError = new Error('Extension 在 evaluate 期间断开');
|
|
241
|
+
await sleepWithinBudget(idleStart, timeout, POLL_INTERVAL);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (readyState === 'complete') {
|
|
245
|
+
idleCompleted = true;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
idleLastError = err instanceof Error ? err : new Error(String(err));
|
|
276
251
|
}
|
|
252
|
+
await sleepWithinBudget(idleStart, timeout, POLL_INTERVAL);
|
|
277
253
|
}
|
|
278
|
-
|
|
279
|
-
|
|
254
|
+
if (!idleCompleted) {
|
|
255
|
+
const msg = `等待页面加载超时 (${timeout}ms)`;
|
|
256
|
+
throw new TimeoutError(idleLastError ? `${msg}: ${idleLastError.message}` : msg);
|
|
280
257
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const timer = setTimeout(() => {
|
|
296
|
-
settled = true;
|
|
297
|
-
resolve(false);
|
|
298
|
-
}, scriptTimeout);
|
|
299
|
-
unifiedSession
|
|
300
|
-
.evaluate(`(function(quietMs, timeoutMs) {
|
|
258
|
+
// Phase 2: DOM mutation 静默检测
|
|
259
|
+
// 注入 MutationObserver,等待 quietPeriod 毫秒内无 DOM 变更
|
|
260
|
+
const mutationRemaining = timeout - (Date.now() - idleStart);
|
|
261
|
+
const scriptTimeout = mutationRemaining - POLL_INTERVAL;
|
|
262
|
+
if (scriptTimeout > quietPeriod) {
|
|
263
|
+
try {
|
|
264
|
+
const domStable = await new Promise((resolve, reject) => {
|
|
265
|
+
let settled = false;
|
|
266
|
+
const timer = setTimeout(() => {
|
|
267
|
+
settled = true;
|
|
268
|
+
resolve(false);
|
|
269
|
+
}, scriptTimeout);
|
|
270
|
+
unifiedSession
|
|
271
|
+
.evaluate(`(function(quietMs, timeoutMs) {
|
|
301
272
|
return new Promise(function(resolve) {
|
|
302
273
|
var target = document.documentElement || document.body;
|
|
303
274
|
if (!target) {
|
|
@@ -342,58 +313,50 @@ async function handleWait(args) {
|
|
|
342
313
|
setTimeout(check, quietMs);
|
|
343
314
|
});
|
|
344
315
|
})`, undefined, mutationRemaining, [quietPeriod, scriptTimeout])
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
316
|
+
.then((value) => {
|
|
317
|
+
if (settled) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
settled = true;
|
|
321
|
+
clearTimeout(timer);
|
|
322
|
+
resolve(value);
|
|
323
|
+
})
|
|
324
|
+
.catch((err) => {
|
|
325
|
+
if (settled) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
settled = true;
|
|
329
|
+
clearTimeout(timer);
|
|
330
|
+
reject(err);
|
|
331
|
+
});
|
|
360
332
|
});
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
333
|
+
if (!domStable) {
|
|
334
|
+
throw new TimeoutError(`等待页面 idle 超时 (${timeout}ms): DOM 未达到 ${quietPeriod}ms 静默`);
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
success: true,
|
|
338
|
+
waited: 'idle',
|
|
339
|
+
domStable,
|
|
340
|
+
mode,
|
|
341
|
+
};
|
|
364
342
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
? {
|
|
372
|
-
diagnostics: await captureDiagnosticsDelta(unifiedSession, diagnosticsStart),
|
|
373
|
-
}
|
|
374
|
-
: {}),
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
catch (err) {
|
|
378
|
-
if (err instanceof TimeoutError) {
|
|
379
|
-
throw err;
|
|
343
|
+
catch (err) {
|
|
344
|
+
if (err instanceof TimeoutError) {
|
|
345
|
+
throw err;
|
|
346
|
+
}
|
|
347
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
348
|
+
throw new TimeoutError(`等待页面 idle 超时 (${timeout}ms): ${message}`);
|
|
380
349
|
}
|
|
381
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
382
|
-
throw new TimeoutError(`等待页面 idle 超时 (${timeout}ms): ${message}`);
|
|
383
350
|
}
|
|
351
|
+
throw new TimeoutError(`等待页面 idle 超时 (${timeout}ms): 剩余时间不足以完成 ${quietPeriod}ms DOM 静默检测`);
|
|
384
352
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
waited: 'idle',
|
|
393
|
-
mode,
|
|
394
|
-
...(diagnosticsStart
|
|
395
|
-
? { diagnostics: await captureDiagnosticsDelta(unifiedSession, diagnosticsStart) }
|
|
396
|
-
: {}),
|
|
353
|
+
const session = getSession();
|
|
354
|
+
await waitForNetworkIdle(session, timeout);
|
|
355
|
+
return {
|
|
356
|
+
success: true,
|
|
357
|
+
waited: 'idle',
|
|
358
|
+
mode,
|
|
359
|
+
};
|
|
397
360
|
});
|
|
398
361
|
}
|
|
399
362
|
default:
|
|
@@ -422,27 +385,38 @@ async function handleWait(args) {
|
|
|
422
385
|
/**
|
|
423
386
|
* Extension 模式:等待元素
|
|
424
387
|
*/
|
|
425
|
-
async function waitForElementExtension(unifiedSession, target, state, timeout) {
|
|
388
|
+
async function waitForElementExtension(unifiedSession, target, state, timeout, frame) {
|
|
426
389
|
const startTime = Date.now();
|
|
427
390
|
const retryDelay = POLL_INTERVAL;
|
|
428
391
|
const { selector, text, xpath, nth: nthParam } = targetToFindParams(target);
|
|
429
392
|
const nth = nthParam ?? 0;
|
|
430
393
|
let lastError = null;
|
|
394
|
+
let matchCount = 0;
|
|
395
|
+
let candidates = [];
|
|
431
396
|
while (true) {
|
|
432
397
|
const elapsed = Date.now() - startTime;
|
|
433
398
|
if (elapsed >= timeout) {
|
|
434
|
-
|
|
435
|
-
|
|
399
|
+
throw new TargetTimeoutError(await buildTargetDiagnostics(unifiedSession, target, {
|
|
400
|
+
nth,
|
|
401
|
+
timeout,
|
|
402
|
+
frame,
|
|
403
|
+
matchCount,
|
|
404
|
+
lastState: state,
|
|
405
|
+
lastError,
|
|
406
|
+
candidates,
|
|
407
|
+
}));
|
|
436
408
|
}
|
|
437
409
|
// 未连接时跳过 find(),避免阻塞超出用户 timeout
|
|
438
410
|
if (!unifiedSession.isExtensionConnected()) {
|
|
439
411
|
lastError = new Error('Extension 未连接');
|
|
440
|
-
await
|
|
412
|
+
await sleepWithinBudget(startTime, timeout, retryDelay);
|
|
441
413
|
continue;
|
|
442
414
|
}
|
|
443
415
|
try {
|
|
444
416
|
const remaining = timeout - elapsed;
|
|
445
417
|
const elements = await unifiedSession.find(selector, text, xpath, remaining);
|
|
418
|
+
matchCount = elements.length;
|
|
419
|
+
candidates = elements;
|
|
446
420
|
const found = elements.length > nth;
|
|
447
421
|
switch (state) {
|
|
448
422
|
case 'attached':
|
|
@@ -481,12 +455,12 @@ async function waitForElementExtension(unifiedSession, target, state, timeout) {
|
|
|
481
455
|
if (err instanceof Error &&
|
|
482
456
|
/Request timeout|Failed to send|disconnect|未连接|stopped|replaced/i.test(err.message)) {
|
|
483
457
|
lastError = err;
|
|
484
|
-
await
|
|
458
|
+
await sleepWithinBudget(startTime, timeout, retryDelay);
|
|
485
459
|
continue;
|
|
486
460
|
}
|
|
487
461
|
throw err;
|
|
488
462
|
}
|
|
489
|
-
await
|
|
463
|
+
await sleepWithinBudget(startTime, timeout, retryDelay);
|
|
490
464
|
}
|
|
491
465
|
}
|
|
492
466
|
/**
|
|
@@ -544,7 +518,7 @@ async function waitForElement(session, target, state, timeout) {
|
|
|
544
518
|
}
|
|
545
519
|
// 继续等待
|
|
546
520
|
}
|
|
547
|
-
await
|
|
521
|
+
await sleepWithinBudget(startTime, timeout, retryDelay);
|
|
548
522
|
}
|
|
549
523
|
throw new TimeoutError(`等待元素 ${JSON.stringify(target)} 状态 ${state} 超时 (${timeout}ms)`);
|
|
550
524
|
}
|