@reconcrap/boss-recommend-mcp 2.0.54 → 2.0.56
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/package.json +1 -1
- package/src/domains/chat/constants.js +9 -24
- package/src/domains/chat/detail.js +34 -186
- package/src/domains/common/account-rights-panel.js +314 -0
- package/src/domains/recommend/constants.js +17 -0
- package/src/domains/recommend/detail.js +379 -72
- package/src/domains/recommend/run-service.js +137 -57
- package/src/domains/recruit/detail.js +15 -0
- package/src/domains/recruit/run-service.js +78 -1
|
@@ -3,23 +3,30 @@ import {
|
|
|
3
3
|
clickPoint,
|
|
4
4
|
DETERMINISTIC_CLICK_OPTIONS,
|
|
5
5
|
getFrameDocumentNodeId,
|
|
6
|
-
getNodeBox,
|
|
7
|
-
getOuterHTML,
|
|
8
|
-
pressKey,
|
|
9
|
-
querySelectorAll,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
6
|
+
getNodeBox,
|
|
7
|
+
getOuterHTML,
|
|
8
|
+
pressKey,
|
|
9
|
+
querySelectorAll,
|
|
10
|
+
scrollNodeIntoView,
|
|
11
|
+
sleep
|
|
12
|
+
} from "../../core/browser/index.js";
|
|
13
|
+
import { candidateKeyFromProfile } from "../../core/infinite-list/index.js";
|
|
14
|
+
import {
|
|
15
|
+
buildScreeningCandidateFromDetail,
|
|
16
|
+
htmlToText
|
|
17
|
+
} from "../../core/screening/index.js";
|
|
18
|
+
import {
|
|
19
|
+
closeBossAccountRightsBlockingPanel,
|
|
20
|
+
findBossAccountRightsBlockingPanel
|
|
21
|
+
} from "../common/account-rights-panel.js";
|
|
22
|
+
import {
|
|
23
|
+
DETAIL_CLOSE_SELECTORS,
|
|
24
|
+
DETAIL_NETWORK_PATTERNS,
|
|
25
|
+
DETAIL_POPUP_SELECTORS,
|
|
26
|
+
DETAIL_RESUME_IFRAME_SELECTORS,
|
|
27
|
+
RECOMMEND_AVATAR_PREVIEW_CLOSE_SELECTORS,
|
|
28
|
+
RECOMMEND_AVATAR_PREVIEW_SELECTORS
|
|
29
|
+
} from "./constants.js";
|
|
23
30
|
import {
|
|
24
31
|
getRecommendRoots
|
|
25
32
|
} from "./roots.js";
|
|
@@ -41,7 +48,7 @@ export function matchesRecommendDetailNetwork(url) {
|
|
|
41
48
|
return DETAIL_NETWORK_PATTERNS.some((pattern) => pattern.test(String(url || "")));
|
|
42
49
|
}
|
|
43
50
|
|
|
44
|
-
export function createRecommendDetailNetworkRecorder(client) {
|
|
51
|
+
export function createRecommendDetailNetworkRecorder(client) {
|
|
45
52
|
const events = [];
|
|
46
53
|
client.Network.responseReceived((event) => {
|
|
47
54
|
const url = event?.response?.url || "";
|
|
@@ -76,9 +83,29 @@ export function createRecommendDetailNetworkRecorder(client) {
|
|
|
76
83
|
events.length = 0;
|
|
77
84
|
}
|
|
78
85
|
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export async function
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function findRecommendBlockingPanel(client, options = {}) {
|
|
89
|
+
return findBossAccountRightsBlockingPanel(client, options);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function closeRecommendBlockingPanels(client, options = {}) {
|
|
93
|
+
return closeBossAccountRightsBlockingPanel(client, {
|
|
94
|
+
resolveRoots: getRecommendRoots,
|
|
95
|
+
...options
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function looksLikeRecommendAvatarPreviewHtml(html = "") {
|
|
100
|
+
return /\bavatar-preview\b|\bfigure-preview\b/i.test(String(html || ""));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isRecommendAvatarPreviewOpenError(error) {
|
|
104
|
+
return error?.code === "RECOMMEND_AVATAR_PREVIEW_OPENED"
|
|
105
|
+
|| /RECOMMEND_AVATAR_PREVIEW_OPENED/i.test(String(error?.message || error || ""));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function waitForRecommendDetailNetworkEvents(recorder, {
|
|
82
109
|
minCount = 1,
|
|
83
110
|
requireLoaded = true,
|
|
84
111
|
timeoutMs = 3500,
|
|
@@ -148,10 +175,10 @@ export async function waitForRecommendDetail(client, {
|
|
|
148
175
|
return lastState;
|
|
149
176
|
}
|
|
150
177
|
|
|
151
|
-
async function readRecommendDetailState(client) {
|
|
152
|
-
const rootState = await getRecommendRoots(client);
|
|
153
|
-
const popup = await findVisibleDetailTarget(client, rootState.roots, DETAIL_POPUP_SELECTORS);
|
|
154
|
-
const resumeIframe = await findVisibleDetailTarget(client, rootState.roots, DETAIL_RESUME_IFRAME_SELECTORS);
|
|
178
|
+
async function readRecommendDetailState(client) {
|
|
179
|
+
const rootState = await getRecommendRoots(client);
|
|
180
|
+
const popup = await findVisibleDetailTarget(client, rootState.roots, DETAIL_POPUP_SELECTORS);
|
|
181
|
+
const resumeIframe = await findVisibleDetailTarget(client, rootState.roots, DETAIL_RESUME_IFRAME_SELECTORS);
|
|
155
182
|
return {
|
|
156
183
|
iframe: rootState.iframe,
|
|
157
184
|
roots: rootState.roots,
|
|
@@ -184,6 +211,46 @@ export async function waitForRecommendDetailClosed(client, {
|
|
|
184
211
|
};
|
|
185
212
|
}
|
|
186
213
|
|
|
214
|
+
export async function readRecommendAvatarPreviewState(client) {
|
|
215
|
+
const rootState = await getRecommendRoots(client);
|
|
216
|
+
const topRoot = rootState.rootNodes?.top
|
|
217
|
+
? [{ name: "top", nodeId: rootState.rootNodes.top }]
|
|
218
|
+
: rootState.roots.filter((root) => root?.name === "top");
|
|
219
|
+
const preview = await findVisibleDetailTarget(client, topRoot, RECOMMEND_AVATAR_PREVIEW_SELECTORS, {
|
|
220
|
+
includeAvatarPreview: true
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
open: Boolean(preview),
|
|
224
|
+
root: rootState.topRoot,
|
|
225
|
+
roots: topRoot,
|
|
226
|
+
preview
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function waitForRecommendAvatarPreviewClosed(client, {
|
|
231
|
+
timeoutMs = 1200,
|
|
232
|
+
intervalMs = 120
|
|
233
|
+
} = {}) {
|
|
234
|
+
const started = Date.now();
|
|
235
|
+
let state = null;
|
|
236
|
+
while (Date.now() - started <= timeoutMs) {
|
|
237
|
+
state = await readRecommendAvatarPreviewState(client);
|
|
238
|
+
if (!state.open) {
|
|
239
|
+
return {
|
|
240
|
+
closed: true,
|
|
241
|
+
elapsed_ms: Date.now() - started,
|
|
242
|
+
state
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
await sleep(intervalMs);
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
closed: false,
|
|
249
|
+
elapsed_ms: Date.now() - started,
|
|
250
|
+
state
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
187
254
|
function compactRect(rect) {
|
|
188
255
|
if (!rect) return null;
|
|
189
256
|
return {
|
|
@@ -239,18 +306,28 @@ async function verifyRecommendDetailStillOpen(client, {
|
|
|
239
306
|
};
|
|
240
307
|
}
|
|
241
308
|
|
|
242
|
-
async function findVisibleDetailTarget(client, roots, selectors
|
|
309
|
+
async function findVisibleDetailTarget(client, roots, selectors, {
|
|
310
|
+
includeAvatarPreview = false
|
|
311
|
+
} = {}) {
|
|
243
312
|
for (const root of roots) {
|
|
244
313
|
if (!root?.nodeId) continue;
|
|
245
|
-
for (const selector of selectors) {
|
|
246
|
-
const nodeIds = await querySelectorAll(client, root.nodeId, selector);
|
|
247
|
-
for (const nodeId of nodeIds) {
|
|
248
|
-
try {
|
|
249
|
-
const box = await getNodeBox(client, nodeId);
|
|
250
|
-
if (box.rect.width > 2 && box.rect.height > 2) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
314
|
+
for (const selector of selectors) {
|
|
315
|
+
const nodeIds = await querySelectorAll(client, root.nodeId, selector);
|
|
316
|
+
for (const nodeId of nodeIds) {
|
|
317
|
+
try {
|
|
318
|
+
const box = await getNodeBox(client, nodeId);
|
|
319
|
+
if (box.rect.width > 2 && box.rect.height > 2) {
|
|
320
|
+
if (!includeAvatarPreview) {
|
|
321
|
+
try {
|
|
322
|
+
const html = await getOuterHTML(client, nodeId);
|
|
323
|
+
if (looksLikeRecommendAvatarPreviewHtml(html)) continue;
|
|
324
|
+
} catch {
|
|
325
|
+
// If the node went stale, let the next candidate decide.
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
root: root.name,
|
|
330
|
+
root_node_id: root.nodeId,
|
|
254
331
|
selector,
|
|
255
332
|
node_id: nodeId,
|
|
256
333
|
center: box.center,
|
|
@@ -317,7 +394,104 @@ export function isStaleRecommendNodeError(error) {
|
|
|
317
394
|
|
|
318
395
|
export function isRecommendDetailOpenMissError(error) {
|
|
319
396
|
const message = String(error?.message || error || "");
|
|
320
|
-
return
|
|
397
|
+
return isRecommendAvatarPreviewOpenError(error)
|
|
398
|
+
|| /Candidate detail did not open|no known detail selectors mounted/i.test(message);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function resolveRecommendCardDetailClickPoint(cardBox, {
|
|
402
|
+
attemptIndex = 0
|
|
403
|
+
} = {}) {
|
|
404
|
+
const rect = cardBox?.rect || {};
|
|
405
|
+
const width = Number(rect.width) || 0;
|
|
406
|
+
const height = Number(rect.height) || 0;
|
|
407
|
+
if (width <= 2 || height <= 2) {
|
|
408
|
+
return {
|
|
409
|
+
...(cardBox?.center || { x: 0, y: 0 }),
|
|
410
|
+
mode: "card-center-fallback",
|
|
411
|
+
reason: "invalid_card_rect"
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const xFractions = [0.22, 0.50, 0.72];
|
|
416
|
+
const xFraction = xFractions[Math.min(Math.max(0, attemptIndex), xFractions.length - 1)];
|
|
417
|
+
const minOffsetX = Math.min(width - 12, Math.max(110, Math.min(180, width * 0.18)));
|
|
418
|
+
const maxOffsetX = Math.max(minOffsetX, width - Math.min(220, Math.max(90, width * 0.22)));
|
|
419
|
+
const rawOffsetX = width * xFraction;
|
|
420
|
+
const offsetX = clampPointCoordinate(rawOffsetX, minOffsetX, maxOffsetX);
|
|
421
|
+
const offsetY = clampPointCoordinate(height * 0.28, Math.min(34, height / 2), Math.max(36, height - 28));
|
|
422
|
+
return {
|
|
423
|
+
x: rect.x + offsetX,
|
|
424
|
+
y: rect.y + offsetY,
|
|
425
|
+
mode: "card-body-safe-point",
|
|
426
|
+
attempt_index: attemptIndex,
|
|
427
|
+
offset_x: Math.round(offsetX),
|
|
428
|
+
offset_y: Math.round(offsetY)
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function clickRecommendCardDetailPoint(client, nodeId, {
|
|
433
|
+
scrollIntoView = true,
|
|
434
|
+
attemptIndex = 0
|
|
435
|
+
} = {}) {
|
|
436
|
+
if (scrollIntoView) {
|
|
437
|
+
try {
|
|
438
|
+
await scrollNodeIntoView(client, nodeId);
|
|
439
|
+
await sleep(150);
|
|
440
|
+
} catch {
|
|
441
|
+
// Recommend list cards are selected from visible nodes; if this CDP
|
|
442
|
+
// helper races the virtual list, let the box lookup/retry decide.
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const box = await getNodeBox(client, nodeId);
|
|
446
|
+
const clickTarget = resolveRecommendCardDetailClickPoint(box, { attemptIndex });
|
|
447
|
+
const clickResult = await clickPoint(client, clickTarget.x, clickTarget.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
448
|
+
return {
|
|
449
|
+
...box,
|
|
450
|
+
click_target: clickTarget,
|
|
451
|
+
click_result: clickResult
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function waitForRecommendDetailOpenOutcome(client, {
|
|
456
|
+
timeoutMs = 10000,
|
|
457
|
+
intervalMs = 250
|
|
458
|
+
} = {}) {
|
|
459
|
+
const started = Date.now();
|
|
460
|
+
let detailState = null;
|
|
461
|
+
let avatarPreview = null;
|
|
462
|
+
while (Date.now() - started <= timeoutMs) {
|
|
463
|
+
detailState = await readRecommendDetailState(client);
|
|
464
|
+
if (detailState?.popup || detailState?.resumeIframe) {
|
|
465
|
+
return {
|
|
466
|
+
kind: "detail",
|
|
467
|
+
elapsed_ms: Date.now() - started,
|
|
468
|
+
detail_state: detailState
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
avatarPreview = await readRecommendAvatarPreviewState(client);
|
|
472
|
+
if (avatarPreview.open) {
|
|
473
|
+
return {
|
|
474
|
+
kind: "avatar_preview",
|
|
475
|
+
elapsed_ms: Date.now() - started,
|
|
476
|
+
avatar_preview: avatarPreview
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
await sleep(intervalMs);
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
kind: "none",
|
|
483
|
+
elapsed_ms: Date.now() - started,
|
|
484
|
+
detail_state: detailState,
|
|
485
|
+
avatar_preview: avatarPreview
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function makeRecommendAvatarPreviewOpenedError(outcome, clickAttempts = []) {
|
|
490
|
+
const error = new Error("RECOMMEND_AVATAR_PREVIEW_OPENED: candidate avatar preview opened instead of resume detail");
|
|
491
|
+
error.code = "RECOMMEND_AVATAR_PREVIEW_OPENED";
|
|
492
|
+
error.avatar_preview = outcome?.avatar_preview || null;
|
|
493
|
+
error.click_attempts = clickAttempts;
|
|
494
|
+
return error;
|
|
321
495
|
}
|
|
322
496
|
|
|
323
497
|
export async function findRecommendCardNodeForCandidateKey(client, {
|
|
@@ -350,8 +524,17 @@ export async function findRecommendCardNodeForCandidateKey(client, {
|
|
|
350
524
|
};
|
|
351
525
|
}
|
|
352
526
|
|
|
353
|
-
|
|
354
|
-
|
|
527
|
+
let nodeIds = [];
|
|
528
|
+
try {
|
|
529
|
+
nodeIds = await findRecommendCardNodeIds(client, frameNodeId);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
lastError = error;
|
|
532
|
+
if (!isStaleRecommendNodeError(error)) throw error;
|
|
533
|
+
rootState = null;
|
|
534
|
+
if (intervalMs > 0) await sleep(intervalMs);
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
lastCardCount = nodeIds.length;
|
|
355
538
|
for (let visibleIndex = 0; visibleIndex < nodeIds.length; visibleIndex += 1) {
|
|
356
539
|
const nodeId = nodeIds[visibleIndex];
|
|
357
540
|
try {
|
|
@@ -397,35 +580,71 @@ export async function findRecommendCardNodeForCandidateKey(client, {
|
|
|
397
580
|
};
|
|
398
581
|
}
|
|
399
582
|
|
|
400
|
-
export async function openRecommendCardDetail(client, cardNodeId, {
|
|
401
|
-
timeoutMs = 12000,
|
|
402
|
-
scrollIntoView = true
|
|
403
|
-
} = {}) {
|
|
404
|
-
const started = Date.now();
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
583
|
+
export async function openRecommendCardDetail(client, cardNodeId, {
|
|
584
|
+
timeoutMs = 12000,
|
|
585
|
+
scrollIntoView = true
|
|
586
|
+
} = {}) {
|
|
587
|
+
const started = Date.now();
|
|
588
|
+
const clickAttempts = [];
|
|
589
|
+
const maxClickAttempts = 3;
|
|
590
|
+
let lastOutcome = null;
|
|
591
|
+
let lastCardBox = null;
|
|
592
|
+
let candidateClickMs = 0;
|
|
593
|
+
let detailOpenMs = 0;
|
|
594
|
+
|
|
595
|
+
for (let attemptIndex = 0; attemptIndex < maxClickAttempts; attemptIndex += 1) {
|
|
596
|
+
const clickStarted = Date.now();
|
|
597
|
+
lastCardBox = await clickRecommendCardDetailPoint(client, cardNodeId, {
|
|
598
|
+
scrollIntoView: attemptIndex === 0 ? scrollIntoView : false,
|
|
599
|
+
attemptIndex
|
|
600
|
+
});
|
|
601
|
+
candidateClickMs += Date.now() - clickStarted;
|
|
602
|
+
const detailStarted = Date.now();
|
|
603
|
+
lastOutcome = await waitForRecommendDetailOpenOutcome(client, {
|
|
604
|
+
timeoutMs: attemptIndex === 0 ? timeoutMs : Math.max(2500, Math.floor(timeoutMs / 3)),
|
|
605
|
+
intervalMs: 250
|
|
606
|
+
});
|
|
607
|
+
detailOpenMs += Date.now() - detailStarted;
|
|
608
|
+
clickAttempts.push({
|
|
609
|
+
attempt: attemptIndex + 1,
|
|
610
|
+
click_target: lastCardBox.click_target,
|
|
611
|
+
click_result: lastCardBox.click_result,
|
|
612
|
+
outcome: lastOutcome.kind,
|
|
613
|
+
elapsed_ms: lastOutcome.elapsed_ms
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
if (lastOutcome.kind === "detail") {
|
|
617
|
+
return {
|
|
618
|
+
card_box: lastCardBox,
|
|
619
|
+
click_attempts: clickAttempts,
|
|
620
|
+
detail_state: lastOutcome.detail_state,
|
|
621
|
+
timings: {
|
|
622
|
+
candidate_click_ms: candidateClickMs,
|
|
623
|
+
detail_open_ms: detailOpenMs,
|
|
624
|
+
open_total_ms: Date.now() - started
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (lastOutcome.kind === "avatar_preview") {
|
|
630
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2, waitMs: 350 });
|
|
631
|
+
throw makeRecommendAvatarPreviewOpenedError(lastOutcome, clickAttempts);
|
|
632
|
+
}
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (lastOutcome?.kind === "avatar_preview") {
|
|
637
|
+
throw makeRecommendAvatarPreviewOpenedError(lastOutcome, clickAttempts);
|
|
638
|
+
}
|
|
639
|
+
const error = new Error("Candidate detail did not open or no known detail selectors mounted");
|
|
640
|
+
error.click_attempts = clickAttempts;
|
|
641
|
+
error.last_open_outcome = lastOutcome;
|
|
642
|
+
throw error;
|
|
643
|
+
}
|
|
425
644
|
|
|
426
|
-
export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
427
|
-
cardNodeId,
|
|
428
|
-
candidateKey = "",
|
|
645
|
+
export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
646
|
+
cardNodeId,
|
|
647
|
+
candidateKey = "",
|
|
429
648
|
cardCandidate = null,
|
|
430
649
|
rootState = null,
|
|
431
650
|
targetUrl = "",
|
|
@@ -492,12 +711,100 @@ export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
|
492
711
|
currentRootState = resolved.root_state || null;
|
|
493
712
|
}
|
|
494
713
|
}
|
|
495
|
-
|
|
496
|
-
throw new Error("Recommend detail retry exhausted");
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
export async function
|
|
500
|
-
attemptsLimit =
|
|
714
|
+
|
|
715
|
+
throw new Error("Recommend detail retry exhausted");
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export async function closeRecommendAvatarPreview(client, {
|
|
719
|
+
attemptsLimit = 2,
|
|
720
|
+
waitMs = 500
|
|
721
|
+
} = {}) {
|
|
722
|
+
const attempts = [];
|
|
723
|
+
for (let index = 0; index < attemptsLimit; index += 1) {
|
|
724
|
+
const state = await readRecommendAvatarPreviewState(client);
|
|
725
|
+
if (!state.open) {
|
|
726
|
+
return {
|
|
727
|
+
closed: true,
|
|
728
|
+
already_closed: true,
|
|
729
|
+
attempts
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const closeTarget = await findVisibleCloseTarget(client, state.roots, RECOMMEND_AVATAR_PREVIEW_CLOSE_SELECTORS);
|
|
734
|
+
if (closeTarget) {
|
|
735
|
+
try {
|
|
736
|
+
if (closeTarget.center) {
|
|
737
|
+
await clickPoint(client, closeTarget.center.x, closeTarget.center.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
738
|
+
} else {
|
|
739
|
+
await clickNodeCenter(client, closeTarget.node_id, DETERMINISTIC_CLICK_OPTIONS);
|
|
740
|
+
}
|
|
741
|
+
attempts.push({
|
|
742
|
+
mode: "avatar-preview-close-selector",
|
|
743
|
+
selector: closeTarget.selector,
|
|
744
|
+
root: closeTarget.root
|
|
745
|
+
});
|
|
746
|
+
} catch (error) {
|
|
747
|
+
attempts.push({
|
|
748
|
+
mode: "avatar-preview-close-selector-error",
|
|
749
|
+
selector: closeTarget.selector,
|
|
750
|
+
root: closeTarget.root,
|
|
751
|
+
error: error?.message || String(error)
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
} else {
|
|
755
|
+
await pressEscape(client);
|
|
756
|
+
attempts.push({ mode: "avatar-preview-Escape" });
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const closed = await waitForRecommendAvatarPreviewClosed(client, {
|
|
760
|
+
timeoutMs: waitMs,
|
|
761
|
+
intervalMs: 100
|
|
762
|
+
});
|
|
763
|
+
attempts.push({
|
|
764
|
+
mode: "wait-avatar-preview-closed",
|
|
765
|
+
closed: closed.closed,
|
|
766
|
+
elapsed_ms: closed.elapsed_ms
|
|
767
|
+
});
|
|
768
|
+
if (closed.closed) {
|
|
769
|
+
return {
|
|
770
|
+
closed: true,
|
|
771
|
+
already_closed: false,
|
|
772
|
+
attempts
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
await pressEscape(client);
|
|
777
|
+
attempts.push({ mode: "avatar-preview-Escape-fallback" });
|
|
778
|
+
const closedAfterEscape = await waitForRecommendAvatarPreviewClosed(client, {
|
|
779
|
+
timeoutMs: waitMs,
|
|
780
|
+
intervalMs: 100
|
|
781
|
+
});
|
|
782
|
+
attempts.push({
|
|
783
|
+
mode: "wait-avatar-preview-closed-after-escape",
|
|
784
|
+
closed: closedAfterEscape.closed,
|
|
785
|
+
elapsed_ms: closedAfterEscape.elapsed_ms
|
|
786
|
+
});
|
|
787
|
+
if (closedAfterEscape.closed) {
|
|
788
|
+
return {
|
|
789
|
+
closed: true,
|
|
790
|
+
already_closed: false,
|
|
791
|
+
attempts
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const state = await readRecommendAvatarPreviewState(client);
|
|
797
|
+
return {
|
|
798
|
+
closed: !state.open,
|
|
799
|
+
already_closed: false,
|
|
800
|
+
reason: state.open ? "avatar_preview_still_visible_after_close_attempts" : null,
|
|
801
|
+
attempts,
|
|
802
|
+
state
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
export async function closeRecommendDetail(client, {
|
|
807
|
+
attemptsLimit = 4,
|
|
501
808
|
closeWaitMs = 5000,
|
|
502
809
|
escapeWaitMs = 3500
|
|
503
810
|
} = {}) {
|