@reconcrap/boss-recommend-mcp 2.0.55 → 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
CHANGED
|
@@ -108,6 +108,23 @@ export const DETAIL_RESUME_IFRAME_SELECTORS = Object.freeze([
|
|
|
108
108
|
'iframe[name*="resume"]'
|
|
109
109
|
]);
|
|
110
110
|
|
|
111
|
+
export const RECOMMEND_AVATAR_PREVIEW_SELECTORS = Object.freeze([
|
|
112
|
+
".boss-dialog__wrapper.avatar-preview",
|
|
113
|
+
".avatar-preview",
|
|
114
|
+
".dialog-wrap.active .avatar-preview",
|
|
115
|
+
".figure-preview"
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
export const RECOMMEND_AVATAR_PREVIEW_CLOSE_SELECTORS = Object.freeze([
|
|
119
|
+
".avatar-preview .boss-popup__close",
|
|
120
|
+
".dialog-wrap.active .avatar-preview .boss-popup__close",
|
|
121
|
+
".dialog-wrap.active .boss-popup__close",
|
|
122
|
+
".boss-dialog__wrapper.avatar-preview .boss-popup__close",
|
|
123
|
+
".boss-popup__close",
|
|
124
|
+
".icon-close",
|
|
125
|
+
'[class*="close"]'
|
|
126
|
+
]);
|
|
127
|
+
|
|
111
128
|
export const DETAIL_CLOSE_SELECTORS = Object.freeze([
|
|
112
129
|
".boss-popup__close",
|
|
113
130
|
".popup-close",
|
|
@@ -3,12 +3,13 @@ import {
|
|
|
3
3
|
clickPoint,
|
|
4
4
|
DETERMINISTIC_CLICK_OPTIONS,
|
|
5
5
|
getFrameDocumentNodeId,
|
|
6
|
-
getNodeBox,
|
|
7
|
-
getOuterHTML,
|
|
8
|
-
pressKey,
|
|
9
|
-
querySelectorAll,
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
getNodeBox,
|
|
7
|
+
getOuterHTML,
|
|
8
|
+
pressKey,
|
|
9
|
+
querySelectorAll,
|
|
10
|
+
scrollNodeIntoView,
|
|
11
|
+
sleep
|
|
12
|
+
} from "../../core/browser/index.js";
|
|
12
13
|
import { candidateKeyFromProfile } from "../../core/infinite-list/index.js";
|
|
13
14
|
import {
|
|
14
15
|
buildScreeningCandidateFromDetail,
|
|
@@ -18,12 +19,14 @@ import {
|
|
|
18
19
|
closeBossAccountRightsBlockingPanel,
|
|
19
20
|
findBossAccountRightsBlockingPanel
|
|
20
21
|
} from "../common/account-rights-panel.js";
|
|
21
|
-
import {
|
|
22
|
-
DETAIL_CLOSE_SELECTORS,
|
|
23
|
-
DETAIL_NETWORK_PATTERNS,
|
|
24
|
-
DETAIL_POPUP_SELECTORS,
|
|
25
|
-
DETAIL_RESUME_IFRAME_SELECTORS
|
|
26
|
-
|
|
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";
|
|
27
30
|
import {
|
|
28
31
|
getRecommendRoots
|
|
29
32
|
} from "./roots.js";
|
|
@@ -93,6 +96,15 @@ export async function closeRecommendBlockingPanels(client, options = {}) {
|
|
|
93
96
|
});
|
|
94
97
|
}
|
|
95
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
|
+
|
|
96
108
|
export async function waitForRecommendDetailNetworkEvents(recorder, {
|
|
97
109
|
minCount = 1,
|
|
98
110
|
requireLoaded = true,
|
|
@@ -163,10 +175,10 @@ export async function waitForRecommendDetail(client, {
|
|
|
163
175
|
return lastState;
|
|
164
176
|
}
|
|
165
177
|
|
|
166
|
-
async function readRecommendDetailState(client) {
|
|
167
|
-
const rootState = await getRecommendRoots(client);
|
|
168
|
-
const popup = await findVisibleDetailTarget(client, rootState.roots, DETAIL_POPUP_SELECTORS);
|
|
169
|
-
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);
|
|
170
182
|
return {
|
|
171
183
|
iframe: rootState.iframe,
|
|
172
184
|
roots: rootState.roots,
|
|
@@ -199,6 +211,46 @@ export async function waitForRecommendDetailClosed(client, {
|
|
|
199
211
|
};
|
|
200
212
|
}
|
|
201
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
|
+
|
|
202
254
|
function compactRect(rect) {
|
|
203
255
|
if (!rect) return null;
|
|
204
256
|
return {
|
|
@@ -254,18 +306,28 @@ async function verifyRecommendDetailStillOpen(client, {
|
|
|
254
306
|
};
|
|
255
307
|
}
|
|
256
308
|
|
|
257
|
-
async function findVisibleDetailTarget(client, roots, selectors
|
|
309
|
+
async function findVisibleDetailTarget(client, roots, selectors, {
|
|
310
|
+
includeAvatarPreview = false
|
|
311
|
+
} = {}) {
|
|
258
312
|
for (const root of roots) {
|
|
259
313
|
if (!root?.nodeId) continue;
|
|
260
|
-
for (const selector of selectors) {
|
|
261
|
-
const nodeIds = await querySelectorAll(client, root.nodeId, selector);
|
|
262
|
-
for (const nodeId of nodeIds) {
|
|
263
|
-
try {
|
|
264
|
-
const box = await getNodeBox(client, nodeId);
|
|
265
|
-
if (box.rect.width > 2 && box.rect.height > 2) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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,
|
|
269
331
|
selector,
|
|
270
332
|
node_id: nodeId,
|
|
271
333
|
center: box.center,
|
|
@@ -332,7 +394,104 @@ export function isStaleRecommendNodeError(error) {
|
|
|
332
394
|
|
|
333
395
|
export function isRecommendDetailOpenMissError(error) {
|
|
334
396
|
const message = String(error?.message || error || "");
|
|
335
|
-
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;
|
|
336
495
|
}
|
|
337
496
|
|
|
338
497
|
export async function findRecommendCardNodeForCandidateKey(client, {
|
|
@@ -365,8 +524,17 @@ export async function findRecommendCardNodeForCandidateKey(client, {
|
|
|
365
524
|
};
|
|
366
525
|
}
|
|
367
526
|
|
|
368
|
-
|
|
369
|
-
|
|
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;
|
|
370
538
|
for (let visibleIndex = 0; visibleIndex < nodeIds.length; visibleIndex += 1) {
|
|
371
539
|
const nodeId = nodeIds[visibleIndex];
|
|
372
540
|
try {
|
|
@@ -412,35 +580,71 @@ export async function findRecommendCardNodeForCandidateKey(client, {
|
|
|
412
580
|
};
|
|
413
581
|
}
|
|
414
582
|
|
|
415
|
-
export async function openRecommendCardDetail(client, cardNodeId, {
|
|
416
|
-
timeoutMs = 12000,
|
|
417
|
-
scrollIntoView = true
|
|
418
|
-
} = {}) {
|
|
419
|
-
const started = Date.now();
|
|
420
|
-
const
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
+
}
|
|
440
644
|
|
|
441
|
-
export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
442
|
-
cardNodeId,
|
|
443
|
-
candidateKey = "",
|
|
645
|
+
export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
646
|
+
cardNodeId,
|
|
647
|
+
candidateKey = "",
|
|
444
648
|
cardCandidate = null,
|
|
445
649
|
rootState = null,
|
|
446
650
|
targetUrl = "",
|
|
@@ -507,12 +711,100 @@ export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
|
507
711
|
currentRootState = resolved.root_state || null;
|
|
508
712
|
}
|
|
509
713
|
}
|
|
510
|
-
|
|
511
|
-
throw new Error("Recommend detail retry exhausted");
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
export async function
|
|
515
|
-
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,
|
|
516
808
|
closeWaitMs = 5000,
|
|
517
809
|
escapeWaitMs = 3500
|
|
518
810
|
} = {}) {
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
} from "../../core/screening/index.js";
|
|
48
48
|
import {
|
|
49
49
|
closeRecommendBlockingPanels,
|
|
50
|
+
closeRecommendAvatarPreview,
|
|
50
51
|
closeRecommendDetail,
|
|
51
52
|
createRecommendDetailNetworkRecorder,
|
|
52
53
|
extractRecommendDetailCandidate,
|
|
@@ -506,6 +507,16 @@ function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
|
506
507
|
if (Array.isArray(error.recommend_detail_open_attempts)) {
|
|
507
508
|
result.recommend_detail_open_attempts = error.recommend_detail_open_attempts;
|
|
508
509
|
}
|
|
510
|
+
if (Array.isArray(error.click_attempts)) {
|
|
511
|
+
result.click_attempts = error.click_attempts;
|
|
512
|
+
}
|
|
513
|
+
if (error.avatar_preview) {
|
|
514
|
+
result.avatar_preview = {
|
|
515
|
+
open: Boolean(error.avatar_preview.open),
|
|
516
|
+
selector: error.avatar_preview.preview?.selector || null,
|
|
517
|
+
rect: error.avatar_preview.preview?.rect || null
|
|
518
|
+
};
|
|
519
|
+
}
|
|
509
520
|
return result;
|
|
510
521
|
}
|
|
511
522
|
|
|
@@ -907,6 +918,7 @@ export async function runRecommendWorkflow({
|
|
|
907
918
|
|
|
908
919
|
runControl.setPhase("recommend:cleanup");
|
|
909
920
|
await closeRecommendDetail(client, { attemptsLimit: 2 });
|
|
921
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 });
|
|
910
922
|
await closeRecommendBlockingPanelsForRun("cleanup");
|
|
911
923
|
|
|
912
924
|
await runControl.waitIfPaused();
|
|
@@ -1247,6 +1259,7 @@ export async function runRecommendWorkflow({
|
|
|
1247
1259
|
timings.image_capture_recovery_trigger = compactError(error, "IMAGE_CAPTURE_FAILED");
|
|
1248
1260
|
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
1249
1261
|
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1262
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1250
1263
|
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
1251
1264
|
await recoverAndReapplyRecommendContext(`image_capture:${detailStep}`, error, {
|
|
1252
1265
|
forceRecentNotView: true
|
|
@@ -1299,6 +1312,7 @@ export async function runRecommendWorkflow({
|
|
|
1299
1312
|
timings.detail_recovery_trigger = compactRecoverableDetailError(error);
|
|
1300
1313
|
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
1301
1314
|
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1315
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1302
1316
|
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
1303
1317
|
await recoverAndReapplyRecommendContext(`detail:${detailStep}`, error, {
|
|
1304
1318
|
forceRecentNotView: true
|
|
@@ -1309,6 +1323,7 @@ export async function runRecommendWorkflow({
|
|
|
1309
1323
|
detailResult = null;
|
|
1310
1324
|
timings.detail_recovered_error = compactRecoverableDetailError(error);
|
|
1311
1325
|
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1326
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1312
1327
|
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
1313
1328
|
}
|
|
1314
1329
|
}
|