@reconcrap/boss-recommend-mcp 2.0.45 → 2.0.46
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/core/browser/index.js +9 -2
- package/src/domains/chat/detail.js +12 -5
- package/src/domains/chat/jobs.js +8 -4
- package/src/domains/chat/run-service.js +22 -0
- package/src/domains/recommend/detail.js +11 -10
- package/src/domains/recommend/filters.js +38 -9
- package/src/domains/recommend/jobs.js +77 -24
- package/src/domains/recommend/refresh.js +1 -1
- package/src/domains/recommend/run-service.js +48 -15
- package/src/domains/recommend/scopes.js +2 -1
- package/src/domains/recruit/detail.js +3 -2
- package/src/domains/recruit/run-service.js +48 -2
- package/src/domains/recruit/search.js +63 -10
package/package.json
CHANGED
|
@@ -42,6 +42,9 @@ const BOSS_LOGIN_DOM_SELECTORS = [
|
|
|
42
42
|
];
|
|
43
43
|
const HUMAN_INTERACTION_CONFIG = new WeakMap();
|
|
44
44
|
const DEFAULT_HUMAN_BEHAVIOR_PROFILE = "paced_with_rests";
|
|
45
|
+
export const DETERMINISTIC_CLICK_OPTIONS = Object.freeze({
|
|
46
|
+
humanRestEnabled: false
|
|
47
|
+
});
|
|
45
48
|
const HUMAN_BEHAVIOR_PROFILES = Object.freeze({
|
|
46
49
|
baseline: Object.freeze({
|
|
47
50
|
enabled: false,
|
|
@@ -1293,8 +1296,12 @@ export async function clickNodeCenter(client, nodeId, {
|
|
|
1293
1296
|
const clickPointTarget = humanClickPointEnabled
|
|
1294
1297
|
? resolveHumanClickPointForBox(box, mergedHumanInteraction)
|
|
1295
1298
|
: { ...box.center, mode: "center" };
|
|
1296
|
-
await clickPoint(client, clickPointTarget.x, clickPointTarget.y, clickOptions);
|
|
1297
|
-
return
|
|
1299
|
+
const clickResult = await clickPoint(client, clickPointTarget.x, clickPointTarget.y, clickOptions);
|
|
1300
|
+
return {
|
|
1301
|
+
...box,
|
|
1302
|
+
click_target: clickPointTarget,
|
|
1303
|
+
click_result: clickResult
|
|
1304
|
+
};
|
|
1298
1305
|
}
|
|
1299
1306
|
|
|
1300
1307
|
export async function pressKey(client, key, {
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
clearFocusedInput,
|
|
3
3
|
clickNodeCenter,
|
|
4
4
|
clickPoint,
|
|
5
|
+
DETERMINISTIC_CLICK_OPTIONS,
|
|
5
6
|
getFrameDocumentNodeId,
|
|
6
7
|
getAttributesMap,
|
|
7
8
|
getNodeBox,
|
|
@@ -572,9 +573,12 @@ export async function selectChatPrimaryLabel(client, {
|
|
|
572
573
|
}
|
|
573
574
|
if (matched) {
|
|
574
575
|
if (matched.center) {
|
|
575
|
-
await clickPoint(client, matched.center.x, matched.center.y);
|
|
576
|
+
await clickPoint(client, matched.center.x, matched.center.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
576
577
|
} else {
|
|
577
|
-
await clickNodeCenter(client, matched.node_id, {
|
|
578
|
+
await clickNodeCenter(client, matched.node_id, {
|
|
579
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
580
|
+
scrollIntoView: true
|
|
581
|
+
});
|
|
578
582
|
}
|
|
579
583
|
if (settleMs > 0) await sleep(settleMs);
|
|
580
584
|
return {
|
|
@@ -633,9 +637,12 @@ export async function selectChatMessageFilter(client, {
|
|
|
633
637
|
const matched = candidates[0];
|
|
634
638
|
if (matched) {
|
|
635
639
|
if (matched.center) {
|
|
636
|
-
await clickPoint(client, matched.center.x, matched.center.y);
|
|
640
|
+
await clickPoint(client, matched.center.x, matched.center.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
637
641
|
} else {
|
|
638
|
-
await clickNodeCenter(client, matched.node_id, {
|
|
642
|
+
await clickNodeCenter(client, matched.node_id, {
|
|
643
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
644
|
+
scrollIntoView: true
|
|
645
|
+
});
|
|
639
646
|
}
|
|
640
647
|
if (settleMs > 0) await sleep(settleMs);
|
|
641
648
|
return {
|
|
@@ -1499,7 +1506,7 @@ export async function closeChatResumeModal(client, {
|
|
|
1499
1506
|
const closeTarget = await findVisibleTarget(client, rootState.roots, CHAT_RESUME_CLOSE_SELECTORS);
|
|
1500
1507
|
if (closeTarget) {
|
|
1501
1508
|
try {
|
|
1502
|
-
await clickPoint(client, closeTarget.center.x, closeTarget.center.y);
|
|
1509
|
+
await clickPoint(client, closeTarget.center.x, closeTarget.center.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
1503
1510
|
attempts.push({
|
|
1504
1511
|
mode: "close-selector",
|
|
1505
1512
|
selector: closeTarget.selector,
|
package/src/domains/chat/jobs.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clickNodeCenter,
|
|
3
3
|
clickPoint,
|
|
4
|
+
DETERMINISTIC_CLICK_OPTIONS,
|
|
4
5
|
getAttributesMap,
|
|
5
6
|
getNodeBox,
|
|
6
7
|
getOuterHTML,
|
|
@@ -240,7 +241,7 @@ async function clickFirstVisible(client, rootNodeId, selectors = []) {
|
|
|
240
241
|
try {
|
|
241
242
|
const box = await getNodeBox(client, nodeId);
|
|
242
243
|
if (box.rect.width <= 2 || box.rect.height <= 2) continue;
|
|
243
|
-
await clickPoint(client, box.center.x, box.center.y);
|
|
244
|
+
await clickPoint(client, box.center.x, box.center.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
244
245
|
return {
|
|
245
246
|
clicked: true,
|
|
246
247
|
selector,
|
|
@@ -265,6 +266,7 @@ async function openChatJobDropdown(client, rootNodeId, {
|
|
|
265
266
|
const started = Date.now();
|
|
266
267
|
const triedPoints = new Set();
|
|
267
268
|
const attempts = [];
|
|
269
|
+
const initialClose = await closeChatJobDropdownQuietly(client, rootNodeId, Math.min(settleMs, 300));
|
|
268
270
|
for (const selector of CHAT_JOB_TRIGGER_SELECTORS) {
|
|
269
271
|
const currentRootNodeId = await freshTopRootNodeId(client, rootNodeId);
|
|
270
272
|
const nodeIds = await safeQuerySelectorAll(client, currentRootNodeId, selector);
|
|
@@ -283,7 +285,7 @@ async function openChatJobDropdown(client, rootNodeId, {
|
|
|
283
285
|
const pointKey = `${nodeId}:${Math.round(x)}:${Math.round(y)}`;
|
|
284
286
|
if (triedPoints.has(pointKey)) continue;
|
|
285
287
|
triedPoints.add(pointKey);
|
|
286
|
-
await clickPoint(client, x, y);
|
|
288
|
+
await clickPoint(client, x, y, DETERMINISTIC_CLICK_OPTIONS);
|
|
287
289
|
if (settleMs > 0) await sleep(Math.min(settleMs, 800));
|
|
288
290
|
const remaining = Math.max(300, timeoutMs - (Date.now() - started));
|
|
289
291
|
const optionsResult = await waitForChatJobOptions(client, currentRootNodeId, {
|
|
@@ -298,7 +300,8 @@ async function openChatJobDropdown(client, rootNodeId, {
|
|
|
298
300
|
node_id: nodeId,
|
|
299
301
|
point: pointName,
|
|
300
302
|
center: { x, y },
|
|
301
|
-
visible_option_count: visibleCount
|
|
303
|
+
visible_option_count: visibleCount,
|
|
304
|
+
initial_close: initialClose
|
|
302
305
|
};
|
|
303
306
|
attempts.push(attempt);
|
|
304
307
|
if (visibleCount > 0) {
|
|
@@ -548,9 +551,10 @@ export async function selectChatJob(client, rootNodeId, {
|
|
|
548
551
|
}
|
|
549
552
|
|
|
550
553
|
if (matched.center) {
|
|
551
|
-
await clickPoint(client, matched.center.x, matched.center.y);
|
|
554
|
+
await clickPoint(client, matched.center.x, matched.center.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
552
555
|
} else {
|
|
553
556
|
await clickNodeCenter(client, matched.node_id, {
|
|
557
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
554
558
|
scrollIntoView: true
|
|
555
559
|
});
|
|
556
560
|
}
|
|
@@ -754,6 +754,7 @@ export async function runChatWorkflow({
|
|
|
754
754
|
let requestSatisfiedCount = 0;
|
|
755
755
|
let requestSkippedCount = 0;
|
|
756
756
|
let contextSetup = {};
|
|
757
|
+
let contextRecoveryAttempts = 0;
|
|
757
758
|
let lastHumanEvent = null;
|
|
758
759
|
|
|
759
760
|
function recordHumanEvent(event = null) {
|
|
@@ -826,6 +827,7 @@ export async function runChatWorkflow({
|
|
|
826
827
|
forceRefresh = false
|
|
827
828
|
} = {}) {
|
|
828
829
|
runControl.setPhase("chat:recover_shell");
|
|
830
|
+
contextRecoveryAttempts += 1;
|
|
829
831
|
const shellRecovery = await recoverChatShell(client, {
|
|
830
832
|
targetUrl,
|
|
831
833
|
timeoutMs: readyTimeoutMs,
|
|
@@ -866,6 +868,7 @@ export async function runChatWorkflow({
|
|
|
866
868
|
const recovery = {
|
|
867
869
|
reason,
|
|
868
870
|
total_refresh: Boolean(forceRefresh),
|
|
871
|
+
attempt: contextRecoveryAttempts,
|
|
869
872
|
shell: shellRecovery,
|
|
870
873
|
candidate_list: candidateList,
|
|
871
874
|
counters
|
|
@@ -917,6 +920,7 @@ export async function runChatWorkflow({
|
|
|
917
920
|
request_skipped: 0,
|
|
918
921
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
919
922
|
scroll_count: compactInfiniteListState(listState).scroll_count,
|
|
923
|
+
context_recoveries: contextRecoveryAttempts,
|
|
920
924
|
list_end_reason: listEndReason,
|
|
921
925
|
viewport_checks: viewportGuard.getStats().checks,
|
|
922
926
|
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
@@ -960,6 +964,7 @@ export async function runChatWorkflow({
|
|
|
960
964
|
requested: requestedCount,
|
|
961
965
|
request_satisfied: requestSatisfiedCount,
|
|
962
966
|
request_skipped: requestSkippedCount,
|
|
967
|
+
context_recoveries: contextRecoveryAttempts,
|
|
963
968
|
results
|
|
964
969
|
};
|
|
965
970
|
}
|
|
@@ -981,6 +986,7 @@ export async function runChatWorkflow({
|
|
|
981
986
|
screening_mode: normalizedScreeningMode,
|
|
982
987
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
983
988
|
scroll_count: 0,
|
|
989
|
+
context_recoveries: contextRecoveryAttempts,
|
|
984
990
|
viewport_checks: viewportGuard.getStats().checks,
|
|
985
991
|
viewport_recoveries: viewportGuard.getStats().recoveries
|
|
986
992
|
});
|
|
@@ -1015,6 +1021,18 @@ export async function runChatWorkflow({
|
|
|
1015
1021
|
const error = new Error(`CHAT_JOB_GUARD_FAILED: requested=${job}; selected=${jobGuard.selected_label || "unknown"}; reason=${jobGuard.reason || "unknown"}`);
|
|
1016
1022
|
error.code = "CHAT_JOB_GUARD_FAILED";
|
|
1017
1023
|
error.chat_job_guard = compactChatJobGuard(jobGuard);
|
|
1024
|
+
runControl.checkpoint({
|
|
1025
|
+
chat_context_step: "job_guard_failed",
|
|
1026
|
+
job_guard: compactChatJobGuard(jobGuard),
|
|
1027
|
+
error: {
|
|
1028
|
+
code: error.code,
|
|
1029
|
+
message: error.message
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
if (contextRecoveryAttempts < 2) {
|
|
1033
|
+
await recoverAndReapplyChatContext("job_guard_failed", error, { forceRefresh: true });
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1018
1036
|
throw error;
|
|
1019
1037
|
}
|
|
1020
1038
|
if (!jobGuard.already_current) {
|
|
@@ -1688,6 +1706,7 @@ export async function runChatWorkflow({
|
|
|
1688
1706
|
request_skipped: requestSkippedCount,
|
|
1689
1707
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
1690
1708
|
scroll_count: compactInfiniteListState(listState).scroll_count,
|
|
1709
|
+
context_recoveries: contextRecoveryAttempts,
|
|
1691
1710
|
list_end_reason: listEndReason || null,
|
|
1692
1711
|
viewport_checks: viewportGuard.getStats().checks,
|
|
1693
1712
|
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
@@ -1739,6 +1758,7 @@ export async function runChatWorkflow({
|
|
|
1739
1758
|
human_rest_count: humanRestController.getState().rest_count,
|
|
1740
1759
|
human_rest_ms: humanRestController.getState().total_rest_ms,
|
|
1741
1760
|
human_rest_last: restResult,
|
|
1761
|
+
context_recoveries: contextRecoveryAttempts,
|
|
1742
1762
|
last_human_event: lastHumanEvent
|
|
1743
1763
|
});
|
|
1744
1764
|
}
|
|
@@ -1781,6 +1801,7 @@ export async function runChatWorkflow({
|
|
|
1781
1801
|
requested: requestedCount,
|
|
1782
1802
|
request_satisfied: requestSatisfiedCount,
|
|
1783
1803
|
request_skipped: requestSkippedCount,
|
|
1804
|
+
context_recoveries: contextRecoveryAttempts,
|
|
1784
1805
|
results
|
|
1785
1806
|
};
|
|
1786
1807
|
}
|
|
@@ -1893,6 +1914,7 @@ export function createChatRunService({
|
|
|
1893
1914
|
requested: 0,
|
|
1894
1915
|
request_satisfied: 0,
|
|
1895
1916
|
request_skipped: 0,
|
|
1917
|
+
context_recoveries: 0,
|
|
1896
1918
|
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1897
1919
|
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1898
1920
|
human_rest_enabled: effectiveHumanRestEnabled,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
clickNodeCenter,
|
|
3
|
-
clickPoint,
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
clickNodeCenter,
|
|
3
|
+
clickPoint,
|
|
4
|
+
DETERMINISTIC_CLICK_OPTIONS,
|
|
5
|
+
getFrameDocumentNodeId,
|
|
5
6
|
getNodeBox,
|
|
6
7
|
getOuterHTML,
|
|
7
8
|
pressKey,
|
|
@@ -507,11 +508,11 @@ export async function closeRecommendDetail(client, {
|
|
|
507
508
|
const closeTarget = await findVisibleCloseTarget(client, rootState.roots, DETAIL_CLOSE_SELECTORS);
|
|
508
509
|
if (closeTarget) {
|
|
509
510
|
try {
|
|
510
|
-
if (closeTarget.center) {
|
|
511
|
-
await clickPoint(client, closeTarget.center.x, closeTarget.center.y);
|
|
512
|
-
} else {
|
|
513
|
-
await clickNodeCenter(client, closeTarget.node_id);
|
|
514
|
-
}
|
|
511
|
+
if (closeTarget.center) {
|
|
512
|
+
await clickPoint(client, closeTarget.center.x, closeTarget.center.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
513
|
+
} else {
|
|
514
|
+
await clickNodeCenter(client, closeTarget.node_id, DETERMINISTIC_CLICK_OPTIONS);
|
|
515
|
+
}
|
|
515
516
|
attempts.push({
|
|
516
517
|
mode: "close-selector",
|
|
517
518
|
selector: closeTarget.selector,
|
|
@@ -732,7 +733,7 @@ async function clickOutsideRecommendDetail(client, detailState) {
|
|
|
732
733
|
root: target?.root || null
|
|
733
734
|
};
|
|
734
735
|
}
|
|
735
|
-
await clickPoint(client, point.x, point.y);
|
|
736
|
+
await clickPoint(client, point.x, point.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
736
737
|
return {
|
|
737
738
|
clicked: true,
|
|
738
739
|
mode: "outside-modal-click",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clickNodeCenter,
|
|
3
3
|
countSelectors,
|
|
4
|
+
DETERMINISTIC_CLICK_OPTIONS,
|
|
4
5
|
findFirstNode,
|
|
5
6
|
getAttributesMap,
|
|
6
7
|
getNodeBox,
|
|
@@ -117,7 +118,7 @@ export async function ensureFilterPanelClosed(client, frameNodeId, triggerNodeId
|
|
|
117
118
|
attempts.push("Escape");
|
|
118
119
|
|
|
119
120
|
if (await getFilterPanelCount(client, frameNodeId) > 0 && triggerNodeId) {
|
|
120
|
-
await clickNodeCenter(client, triggerNodeId);
|
|
121
|
+
await clickNodeCenter(client, triggerNodeId, DETERMINISTIC_CLICK_OPTIONS);
|
|
121
122
|
await sleep(500);
|
|
122
123
|
attempts.push("filter-trigger-toggle");
|
|
123
124
|
}
|
|
@@ -125,6 +126,19 @@ export async function ensureFilterPanelClosed(client, frameNodeId, triggerNodeId
|
|
|
125
126
|
return attempts;
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
async function dismissRecommendControlOverlays(client, settleMs = 250) {
|
|
130
|
+
if (typeof client?.Input?.dispatchKeyEvent !== "function") {
|
|
131
|
+
return ["Escape-unavailable"];
|
|
132
|
+
}
|
|
133
|
+
await pressKey(client, "Escape", {
|
|
134
|
+
code: "Escape",
|
|
135
|
+
windowsVirtualKeyCode: 27,
|
|
136
|
+
nativeVirtualKeyCode: 27
|
|
137
|
+
});
|
|
138
|
+
if (settleMs > 0) await sleep(settleMs);
|
|
139
|
+
return ["Escape"];
|
|
140
|
+
}
|
|
141
|
+
|
|
128
142
|
async function findFilterTriggerCandidates(client, frameNodeId) {
|
|
129
143
|
const candidates = [];
|
|
130
144
|
const seen = new Set();
|
|
@@ -147,6 +161,7 @@ export async function openFilterPanel(client, frameNodeId) {
|
|
|
147
161
|
throw new Error("Recommend filter trigger was not found");
|
|
148
162
|
}
|
|
149
163
|
|
|
164
|
+
const preOpenDismissalAttempts = await dismissRecommendControlOverlays(client);
|
|
150
165
|
const existingPanelNodeId = await waitForSelector(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.panel, {
|
|
151
166
|
timeoutMs: 300,
|
|
152
167
|
intervalMs: 100
|
|
@@ -157,7 +172,7 @@ export async function openFilterPanel(client, frameNodeId) {
|
|
|
157
172
|
trigger: triggerCandidates[0],
|
|
158
173
|
trigger_box: triggerBox,
|
|
159
174
|
panel_node_id: existingPanelNodeId,
|
|
160
|
-
initial_close_attempts:
|
|
175
|
+
initial_close_attempts: preOpenDismissalAttempts,
|
|
161
176
|
already_open: true
|
|
162
177
|
};
|
|
163
178
|
}
|
|
@@ -169,11 +184,13 @@ export async function openFilterPanel(client, frameNodeId) {
|
|
|
169
184
|
triggerCandidates = await findFilterTriggerCandidates(client, frameNodeId);
|
|
170
185
|
for (const trigger of triggerCandidates) {
|
|
171
186
|
const triggerBox = await getNodeBox(client, trigger.nodeId);
|
|
172
|
-
await clickNodeCenter(client, trigger.nodeId);
|
|
187
|
+
const clickBox = await clickNodeCenter(client, trigger.nodeId, DETERMINISTIC_CLICK_OPTIONS);
|
|
173
188
|
attempts.push({
|
|
174
189
|
selector: trigger.selector,
|
|
175
190
|
node_id: trigger.nodeId,
|
|
176
|
-
center: triggerBox.center
|
|
191
|
+
center: triggerBox.center,
|
|
192
|
+
click_target: clickBox.click_target,
|
|
193
|
+
click_result: clickBox.click_result
|
|
177
194
|
});
|
|
178
195
|
const panelNodeId = await waitForSelector(client, frameNodeId, RECOMMEND_FILTER_SELECTORS.panel, {
|
|
179
196
|
timeoutMs: 2500,
|
|
@@ -184,7 +201,10 @@ export async function openFilterPanel(client, frameNodeId) {
|
|
|
184
201
|
trigger,
|
|
185
202
|
trigger_box: triggerBox,
|
|
186
203
|
panel_node_id: panelNodeId,
|
|
187
|
-
initial_close_attempts:
|
|
204
|
+
initial_close_attempts: [
|
|
205
|
+
...preOpenDismissalAttempts,
|
|
206
|
+
...closeAttempts
|
|
207
|
+
],
|
|
188
208
|
open_attempts: attempts
|
|
189
209
|
};
|
|
190
210
|
}
|
|
@@ -236,7 +256,7 @@ async function clickFirstAvailableNode(client, nodeIds) {
|
|
|
236
256
|
const errors = [];
|
|
237
257
|
for (const nodeId of nodeIds) {
|
|
238
258
|
try {
|
|
239
|
-
const box = await clickNodeCenter(client, nodeId);
|
|
259
|
+
const box = await clickNodeCenter(client, nodeId, DETERMINISTIC_CLICK_OPTIONS);
|
|
240
260
|
return {
|
|
241
261
|
clicked: true,
|
|
242
262
|
node_id: nodeId,
|
|
@@ -301,7 +321,10 @@ export async function selectFirstSafeFilterOption(client, frameNodeId, {
|
|
|
301
321
|
throw new Error("No safe non-active recommend filter option was found");
|
|
302
322
|
}
|
|
303
323
|
|
|
304
|
-
const box = await clickNodeCenter(client, selected.node_id, {
|
|
324
|
+
const box = await clickNodeCenter(client, selected.node_id, {
|
|
325
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
326
|
+
scrollIntoView: true
|
|
327
|
+
});
|
|
305
328
|
await sleep(300);
|
|
306
329
|
|
|
307
330
|
return {
|
|
@@ -338,7 +361,10 @@ export async function selectFilterOption(client, frameNodeId, {
|
|
|
338
361
|
throw new Error(`No matching recommend filter option was found for ${target}`);
|
|
339
362
|
}
|
|
340
363
|
|
|
341
|
-
const box = await clickNodeCenter(client, selected.node_id, {
|
|
364
|
+
const box = await clickNodeCenter(client, selected.node_id, {
|
|
365
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
366
|
+
scrollIntoView: true
|
|
367
|
+
});
|
|
342
368
|
await sleep(300);
|
|
343
369
|
|
|
344
370
|
return {
|
|
@@ -409,7 +435,10 @@ export async function selectFilterOptions(client, frameNodeId, {
|
|
|
409
435
|
continue;
|
|
410
436
|
}
|
|
411
437
|
|
|
412
|
-
const box = await clickNodeCenter(client, selected.node_id, {
|
|
438
|
+
const box = await clickNodeCenter(client, selected.node_id, {
|
|
439
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
440
|
+
scrollIntoView: true
|
|
441
|
+
});
|
|
413
442
|
selectedOptions.push({
|
|
414
443
|
group: selected.group,
|
|
415
444
|
label: selected.label,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clickNodeCenter,
|
|
3
|
+
DETERMINISTIC_CLICK_OPTIONS,
|
|
3
4
|
getAttributesMap,
|
|
4
5
|
getNodeBox,
|
|
5
6
|
getOuterHTML,
|
|
6
7
|
pressKey,
|
|
7
8
|
querySelectorAll,
|
|
8
|
-
sleep
|
|
9
|
-
waitForSelector
|
|
9
|
+
sleep
|
|
10
10
|
} from "../../core/browser/index.js";
|
|
11
11
|
import {
|
|
12
12
|
htmlToText,
|
|
@@ -108,7 +108,9 @@ export async function waitForRecommendJobTrigger(client, frameNodeId, {
|
|
|
108
108
|
export async function openRecommendJobDropdown(client, frameNodeId, {
|
|
109
109
|
timeoutMs = 4000,
|
|
110
110
|
triggerTimeoutMs = Math.max(8000, timeoutMs),
|
|
111
|
-
triggerIntervalMs = 250
|
|
111
|
+
triggerIntervalMs = 250,
|
|
112
|
+
dismissBeforeOpen = true,
|
|
113
|
+
maxAttempts = 3
|
|
112
114
|
} = {}) {
|
|
113
115
|
const trigger = await waitForRecommendJobTrigger(client, frameNodeId, {
|
|
114
116
|
timeoutMs: triggerTimeoutMs,
|
|
@@ -118,36 +120,72 @@ export async function openRecommendJobDropdown(client, frameNodeId, {
|
|
|
118
120
|
throw new Error("Recommend job trigger was not found");
|
|
119
121
|
}
|
|
120
122
|
|
|
121
|
-
|
|
123
|
+
const alreadyOpen = await waitForVisibleRecommendJobOptions(client, frameNodeId, {
|
|
122
124
|
timeoutMs: 300,
|
|
123
125
|
intervalMs: 100
|
|
124
126
|
});
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
127
|
+
if (alreadyOpen.visible_options.length) {
|
|
128
|
+
return {
|
|
129
|
+
opened: true,
|
|
130
|
+
already_open: true,
|
|
131
|
+
trigger,
|
|
132
|
+
options: alreadyOpen.options
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const attempts = [];
|
|
137
|
+
const attemptLimit = Math.max(1, Math.floor(Number(maxAttempts) || 1));
|
|
138
|
+
if (dismissBeforeOpen) {
|
|
139
|
+
await closeRecommendJobDropdown(client);
|
|
140
|
+
}
|
|
141
|
+
for (let attempt = 1; attempt <= attemptLimit; attempt += 1) {
|
|
142
|
+
if (attempt > 1) await closeRecommendJobDropdown(client);
|
|
143
|
+
const triggerBox = await clickNodeCenter(client, trigger.node_id, DETERMINISTIC_CLICK_OPTIONS);
|
|
144
|
+
const opened = await waitForVisibleRecommendJobOptions(client, frameNodeId, {
|
|
145
|
+
timeoutMs,
|
|
146
|
+
intervalMs: 200
|
|
147
|
+
});
|
|
148
|
+
attempts.push({
|
|
149
|
+
attempt,
|
|
150
|
+
trigger_box: triggerBox,
|
|
151
|
+
option_count: opened.options.length,
|
|
152
|
+
visible_option_count: opened.visible_options.length
|
|
153
|
+
});
|
|
154
|
+
if (opened.visible_options.length) {
|
|
128
155
|
return {
|
|
129
156
|
opened: true,
|
|
130
|
-
already_open:
|
|
157
|
+
already_open: false,
|
|
131
158
|
trigger,
|
|
132
|
-
options
|
|
159
|
+
options: opened.options,
|
|
160
|
+
attempts
|
|
133
161
|
};
|
|
134
162
|
}
|
|
135
163
|
}
|
|
164
|
+
const error = new Error("Recommend job dropdown did not expose visible options after trigger click");
|
|
165
|
+
error.job_dropdown_attempts = attempts;
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
136
168
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
169
|
+
async function waitForVisibleRecommendJobOptions(client, frameNodeId, {
|
|
170
|
+
timeoutMs = 4000,
|
|
171
|
+
intervalMs = 200
|
|
172
|
+
} = {}) {
|
|
173
|
+
const started = Date.now();
|
|
174
|
+
let lastOptions = [];
|
|
175
|
+
while (Date.now() - started <= timeoutMs) {
|
|
176
|
+
lastOptions = await listRecommendJobOptions(client, frameNodeId, { openDropdown: false });
|
|
177
|
+
const visibleOptions = lastOptions.filter((option) => option.visible);
|
|
178
|
+
if (visibleOptions.length) {
|
|
179
|
+
return {
|
|
180
|
+
options: lastOptions,
|
|
181
|
+
visible_options: visibleOptions
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
await sleep(intervalMs);
|
|
144
185
|
}
|
|
145
|
-
const options = await listRecommendJobOptions(client, frameNodeId, { openDropdown: false });
|
|
146
186
|
return {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
trigger,
|
|
150
|
-
options
|
|
187
|
+
options: lastOptions,
|
|
188
|
+
visible_options: []
|
|
151
189
|
};
|
|
152
190
|
}
|
|
153
191
|
|
|
@@ -174,12 +212,22 @@ export async function listRecommendJobOptions(client, frameNodeId, {
|
|
|
174
212
|
}
|
|
175
213
|
|
|
176
214
|
export async function closeRecommendJobDropdown(client) {
|
|
215
|
+
if (typeof client?.Input?.dispatchKeyEvent !== "function") {
|
|
216
|
+
return {
|
|
217
|
+
ok: false,
|
|
218
|
+
reason: "dispatch_key_unavailable"
|
|
219
|
+
};
|
|
220
|
+
}
|
|
177
221
|
await pressKey(client, "Escape", {
|
|
178
222
|
code: "Escape",
|
|
179
223
|
windowsVirtualKeyCode: 27,
|
|
180
224
|
nativeVirtualKeyCode: 27
|
|
181
225
|
});
|
|
182
226
|
await sleep(300);
|
|
227
|
+
return {
|
|
228
|
+
ok: true,
|
|
229
|
+
reason: "escape"
|
|
230
|
+
};
|
|
183
231
|
}
|
|
184
232
|
|
|
185
233
|
export async function selectRecommendJob(client, frameNodeId, {
|
|
@@ -205,11 +253,16 @@ export async function selectRecommendJob(client, frameNodeId, {
|
|
|
205
253
|
? opened.options
|
|
206
254
|
: await listRecommendJobOptions(client, frameNodeId, { openDropdown: false });
|
|
207
255
|
const visibleOptions = options.filter((option) => option.visible);
|
|
208
|
-
const
|
|
209
|
-
|
|
256
|
+
const hiddenMatches = options.filter((option) => !option.visible && jobLabelMatches(option.label, target));
|
|
257
|
+
const match = visibleOptions.find((option) => jobLabelMatches(option.label, target));
|
|
210
258
|
|
|
211
259
|
if (!match) {
|
|
212
260
|
await closeRecommendJobDropdown(client);
|
|
261
|
+
if (hiddenMatches.length) {
|
|
262
|
+
const error = new Error(`Matched recommend job has no visible clickable option: ${hiddenMatches[0].label}`);
|
|
263
|
+
error.hidden_job_matches = hiddenMatches.map(compactJobOption);
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
213
266
|
return {
|
|
214
267
|
requested: target,
|
|
215
268
|
selected: false,
|
|
@@ -234,7 +287,7 @@ export async function selectRecommendJob(client, frameNodeId, {
|
|
|
234
287
|
throw new Error(`Matched recommend job has no clickable center: ${match.label}`);
|
|
235
288
|
}
|
|
236
289
|
|
|
237
|
-
const clickedBox = await clickNodeCenter(client, match.node_id);
|
|
290
|
+
const clickedBox = await clickNodeCenter(client, match.node_id, DETERMINISTIC_CLICK_OPTIONS);
|
|
238
291
|
if (settleMs > 0) await sleep(settleMs);
|
|
239
292
|
return {
|
|
240
293
|
requested: target,
|
|
@@ -103,7 +103,7 @@ function compactFilterReapplyError(error) {
|
|
|
103
103
|
|
|
104
104
|
export function isRetryableRecommendJobSelectionError(error) {
|
|
105
105
|
const message = String(error?.message || error || "");
|
|
106
|
-
return /Recommend job trigger was not found|Recommend job dropdown did not mount options/i.test(message);
|
|
106
|
+
return /Recommend job trigger was not found|Recommend job dropdown did not mount options|Recommend job dropdown did not expose visible options|Matched recommend job has no clickable center|Matched recommend job has no visible clickable option/i.test(message);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
function compactJobSelectionAttempt({
|
|
@@ -449,15 +449,43 @@ function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
|
449
449
|
if (error.close_result) {
|
|
450
450
|
result.close_result = compactCloseResult(error.close_result);
|
|
451
451
|
}
|
|
452
|
+
if (error.refresh_attempt) {
|
|
453
|
+
result.refresh_attempt = error.refresh_attempt;
|
|
454
|
+
}
|
|
455
|
+
if (error.list_end_reason) {
|
|
456
|
+
result.list_end_reason = error.list_end_reason;
|
|
457
|
+
}
|
|
458
|
+
if (error.target_count != null) {
|
|
459
|
+
result.target_count = error.target_count;
|
|
460
|
+
}
|
|
461
|
+
if (error.passed_count != null) {
|
|
462
|
+
result.passed_count = error.passed_count;
|
|
463
|
+
}
|
|
452
464
|
return result;
|
|
453
465
|
}
|
|
454
466
|
|
|
455
|
-
function createRecommendCloseFailureError(closeResult) {
|
|
456
|
-
const error = new Error(closeResult?.reason || "Recommend detail did not close before recovery");
|
|
457
|
-
error.code = "DETAIL_CLOSE_FAILED";
|
|
458
|
-
error.close_result = closeResult || null;
|
|
459
|
-
return error;
|
|
460
|
-
}
|
|
467
|
+
function createRecommendCloseFailureError(closeResult) {
|
|
468
|
+
const error = new Error(closeResult?.reason || "Recommend detail did not close before recovery");
|
|
469
|
+
error.code = "DETAIL_CLOSE_FAILED";
|
|
470
|
+
error.close_result = closeResult || null;
|
|
471
|
+
return error;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function createRecommendRefreshFailureError(refreshAttempt, {
|
|
475
|
+
listEndReason = "",
|
|
476
|
+
targetCount = 0,
|
|
477
|
+
passedCount = 0
|
|
478
|
+
} = {}) {
|
|
479
|
+
const reason = refreshAttempt?.reason || "refresh_failed";
|
|
480
|
+
const detail = refreshAttempt?.error ? `: ${refreshAttempt.error}` : "";
|
|
481
|
+
const error = new Error(`Recommend refresh failed before target was reached (${reason}${detail})`);
|
|
482
|
+
error.code = "RECOMMEND_END_REFRESH_FAILED";
|
|
483
|
+
error.refresh_attempt = refreshAttempt || null;
|
|
484
|
+
error.list_end_reason = listEndReason || null;
|
|
485
|
+
error.target_count = targetCount;
|
|
486
|
+
error.passed_count = passedCount;
|
|
487
|
+
return error;
|
|
488
|
+
}
|
|
461
489
|
|
|
462
490
|
export function isRecoverableImageCaptureError(error) {
|
|
463
491
|
const code = String(error?.code || "");
|
|
@@ -960,9 +988,9 @@ export async function runRecommendWorkflow({
|
|
|
960
988
|
refresh_forced_recent_not_view: true,
|
|
961
989
|
list_end_reason: listEndReason
|
|
962
990
|
});
|
|
963
|
-
if (refreshResult.ok) {
|
|
964
|
-
rootState = refreshResult.root_state || await getRecommendRoots(client);
|
|
965
|
-
rootState = await ensureRecommendViewport(rootState, "refresh_after");
|
|
991
|
+
if (refreshResult.ok) {
|
|
992
|
+
rootState = refreshResult.root_state || await getRecommendRoots(client);
|
|
993
|
+
rootState = await ensureRecommendViewport(rootState, "refresh_after");
|
|
966
994
|
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
967
995
|
timeoutMs: cardTimeoutMs,
|
|
968
996
|
intervalMs: 300
|
|
@@ -976,12 +1004,17 @@ export async function runRecommendWorkflow({
|
|
|
976
1004
|
forced_recent_not_view: true
|
|
977
1005
|
}
|
|
978
1006
|
});
|
|
979
|
-
listEndReason = "";
|
|
980
|
-
continue;
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1007
|
+
listEndReason = "";
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
throw createRecommendRefreshFailureError(compactRefresh, {
|
|
1011
|
+
listEndReason,
|
|
1012
|
+
targetCount: targetPassCount,
|
|
1013
|
+
passedCount: countPassedResults(results)
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
break;
|
|
1017
|
+
}
|
|
985
1018
|
|
|
986
1019
|
const index = results.length;
|
|
987
1020
|
let cardNodeId = nextCandidateResult.item.node_id;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clickNodeCenter,
|
|
3
|
+
DETERMINISTIC_CLICK_OPTIONS,
|
|
3
4
|
getAttributesMap,
|
|
4
5
|
getNodeBox,
|
|
5
6
|
getOuterHTML,
|
|
@@ -220,7 +221,7 @@ export async function selectRecommendPageScope(client, frameNodeId, {
|
|
|
220
221
|
};
|
|
221
222
|
}
|
|
222
223
|
|
|
223
|
-
const clickBox = await clickNodeCenter(client, targetTab.node_id);
|
|
224
|
+
const clickBox = await clickNodeCenter(client, targetTab.node_id, DETERMINISTIC_CLICK_OPTIONS);
|
|
224
225
|
if (settleMs > 0) await sleep(settleMs);
|
|
225
226
|
const after = await waitForRecommendPageScope(client, frameNodeId, effectiveScope, {
|
|
226
227
|
timeoutMs,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
clickNodeCenter,
|
|
3
3
|
clickPoint,
|
|
4
|
+
DETERMINISTIC_CLICK_OPTIONS,
|
|
4
5
|
getFrameDocumentNodeId,
|
|
5
6
|
getNodeBox,
|
|
6
7
|
getOuterHTML,
|
|
@@ -305,9 +306,9 @@ export async function closeRecruitDetail(client, {
|
|
|
305
306
|
if (closeTarget) {
|
|
306
307
|
try {
|
|
307
308
|
if (closeTarget.center) {
|
|
308
|
-
await clickPoint(client, closeTarget.center.x, closeTarget.center.y);
|
|
309
|
+
await clickPoint(client, closeTarget.center.x, closeTarget.center.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
309
310
|
} else {
|
|
310
|
-
await clickNodeCenter(client, closeTarget.node_id);
|
|
311
|
+
await clickNodeCenter(client, closeTarget.node_id, DETERMINISTIC_CLICK_OPTIONS);
|
|
311
312
|
}
|
|
312
313
|
attempts.push({
|
|
313
314
|
mode: "close-selector",
|
|
@@ -147,10 +147,23 @@ function compactRefreshAttempt(refreshAttempt) {
|
|
|
147
147
|
|
|
148
148
|
function compactError(error, fallbackCode = "RECRUIT_RUN_ERROR") {
|
|
149
149
|
if (!error) return null;
|
|
150
|
-
|
|
150
|
+
const result = {
|
|
151
151
|
code: error.code || fallbackCode,
|
|
152
152
|
message: error.message || String(error)
|
|
153
153
|
};
|
|
154
|
+
if (error.refresh_attempt) {
|
|
155
|
+
result.refresh_attempt = error.refresh_attempt;
|
|
156
|
+
}
|
|
157
|
+
if (error.list_end_reason) {
|
|
158
|
+
result.list_end_reason = error.list_end_reason;
|
|
159
|
+
}
|
|
160
|
+
if (error.target_count != null) {
|
|
161
|
+
result.target_count = error.target_count;
|
|
162
|
+
}
|
|
163
|
+
if (error.processed_count != null) {
|
|
164
|
+
result.processed_count = error.processed_count;
|
|
165
|
+
}
|
|
166
|
+
return result;
|
|
154
167
|
}
|
|
155
168
|
|
|
156
169
|
function createRecruitCloseFailureError(closeResult) {
|
|
@@ -160,6 +173,34 @@ function createRecruitCloseFailureError(closeResult) {
|
|
|
160
173
|
return error;
|
|
161
174
|
}
|
|
162
175
|
|
|
176
|
+
function createRecruitRefreshFailureError(refreshAttempt, {
|
|
177
|
+
listEndReason = "",
|
|
178
|
+
targetCount = 0,
|
|
179
|
+
processedCount = 0
|
|
180
|
+
} = {}) {
|
|
181
|
+
const reason = refreshAttempt?.application?.post_search_state?.ok === false
|
|
182
|
+
? "search_result_not_ready"
|
|
183
|
+
: refreshAttempt?.application?.post_search_state?.counts?.candidate_card === 0
|
|
184
|
+
? "no_cards_after_refresh"
|
|
185
|
+
: "refresh_failed";
|
|
186
|
+
const error = new Error(`Recruit/search refresh failed before target was reached (${reason})`);
|
|
187
|
+
error.code = "RECRUIT_END_REFRESH_FAILED";
|
|
188
|
+
error.refresh_attempt = refreshAttempt || null;
|
|
189
|
+
error.list_end_reason = listEndReason || null;
|
|
190
|
+
error.target_count = targetCount;
|
|
191
|
+
error.processed_count = processedCount;
|
|
192
|
+
return error;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isRefreshableListStall(reason = "") {
|
|
196
|
+
return new Set([
|
|
197
|
+
"stable_visible_signature",
|
|
198
|
+
"max_scrolls_exhausted",
|
|
199
|
+
"scroll_failed",
|
|
200
|
+
"scroll_anchor_unavailable"
|
|
201
|
+
]).has(String(reason || ""));
|
|
202
|
+
}
|
|
203
|
+
|
|
163
204
|
export function isStaleRecruitNodeError(error) {
|
|
164
205
|
const message = String(error?.message || error || "");
|
|
165
206
|
return /Could not find node with given id|No node with given id|Node is detached|Cannot find node/i.test(message);
|
|
@@ -610,7 +651,7 @@ export async function runRecruitWorkflow({
|
|
|
610
651
|
if (!nextCandidateResult.ok) {
|
|
611
652
|
listEndReason = nextCandidateResult.reason || "list_exhausted";
|
|
612
653
|
if (
|
|
613
|
-
nextCandidateResult.end_reached
|
|
654
|
+
(nextCandidateResult.end_reached || isRefreshableListStall(nextCandidateResult.reason))
|
|
614
655
|
&& refreshOnEnd
|
|
615
656
|
&& results.length < limit
|
|
616
657
|
&& refreshRounds < Math.max(0, Number(maxRefreshRounds) || 0)
|
|
@@ -658,6 +699,11 @@ export async function runRecruitWorkflow({
|
|
|
658
699
|
listEndReason = "";
|
|
659
700
|
continue;
|
|
660
701
|
}
|
|
702
|
+
throw createRecruitRefreshFailureError(compactRefresh, {
|
|
703
|
+
listEndReason,
|
|
704
|
+
targetCount: limit,
|
|
705
|
+
processedCount: results.length
|
|
706
|
+
});
|
|
661
707
|
}
|
|
662
708
|
break;
|
|
663
709
|
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
clearFocusedInput,
|
|
3
3
|
clickNodeCenter,
|
|
4
4
|
countSelectors,
|
|
5
|
+
DETERMINISTIC_CLICK_OPTIONS,
|
|
5
6
|
describeNode,
|
|
6
7
|
findFirstNode,
|
|
7
8
|
getAttributesMap,
|
|
@@ -315,7 +316,10 @@ async function clickFirstNodeBySelectors(client, rootNodeId, selectors, {
|
|
|
315
316
|
throw new Error(`Recruit search node was not found for selectors: ${selectors.join(", ")}`);
|
|
316
317
|
}
|
|
317
318
|
try {
|
|
318
|
-
const box = await clickNodeCenter(client, found.nodeId, {
|
|
319
|
+
const box = await clickNodeCenter(client, found.nodeId, {
|
|
320
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
321
|
+
scrollIntoView
|
|
322
|
+
});
|
|
319
323
|
await sleep(250);
|
|
320
324
|
return {
|
|
321
325
|
clicked: true,
|
|
@@ -337,6 +341,26 @@ async function clickFirstNodeBySelectors(client, rootNodeId, selectors, {
|
|
|
337
341
|
}
|
|
338
342
|
}
|
|
339
343
|
|
|
344
|
+
async function dismissRecruitSearchOverlays(client, settleMs = 250) {
|
|
345
|
+
if (typeof client?.Input?.dispatchKeyEvent !== "function") {
|
|
346
|
+
return {
|
|
347
|
+
method: "Escape",
|
|
348
|
+
skipped: true,
|
|
349
|
+
reason: "dispatch_key_unavailable"
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
await pressKey(client, "Escape", {
|
|
353
|
+
code: "Escape",
|
|
354
|
+
windowsVirtualKeyCode: 27,
|
|
355
|
+
nativeVirtualKeyCode: 27
|
|
356
|
+
});
|
|
357
|
+
if (settleMs > 0) await sleep(settleMs);
|
|
358
|
+
return {
|
|
359
|
+
method: "Escape",
|
|
360
|
+
settle_ms: settleMs
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
340
364
|
export async function getRecruitSearchCounts(client, frameNodeId) {
|
|
341
365
|
return countSelectors(client, frameNodeId, {
|
|
342
366
|
keyword_input: RECRUIT_SEARCH_SELECTORS.keywordInput.join(", "),
|
|
@@ -495,7 +519,10 @@ export async function setRecruitJobTitle(client, frameNodeId, jobTitle, {
|
|
|
495
519
|
}
|
|
496
520
|
let box = null;
|
|
497
521
|
if (!lookup.candidate.active) {
|
|
498
|
-
box = await clickNodeCenter(client, lookup.candidate.node_id, {
|
|
522
|
+
box = await clickNodeCenter(client, lookup.candidate.node_id, {
|
|
523
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
524
|
+
scrollIntoView: true
|
|
525
|
+
});
|
|
499
526
|
await sleep(500);
|
|
500
527
|
}
|
|
501
528
|
return {
|
|
@@ -527,7 +554,10 @@ export async function setRecruitDegree(client, frameNodeId, degree) {
|
|
|
527
554
|
if (!candidate) {
|
|
528
555
|
throw new Error(`Recruit degree option was not found: ${degreeLabel}`);
|
|
529
556
|
}
|
|
530
|
-
const box = await clickNodeCenter(client, candidate.node_id, {
|
|
557
|
+
const box = await clickNodeCenter(client, candidate.node_id, {
|
|
558
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
559
|
+
scrollIntoView: true
|
|
560
|
+
});
|
|
531
561
|
await sleep(350);
|
|
532
562
|
return {
|
|
533
563
|
applied: true,
|
|
@@ -573,7 +603,10 @@ export async function setRecruitDegrees(client, frameNodeId, degrees = []) {
|
|
|
573
603
|
|
|
574
604
|
let box = null;
|
|
575
605
|
if (!candidate.active) {
|
|
576
|
-
box = await clickNodeCenter(client, candidate.node_id, {
|
|
606
|
+
box = await clickNodeCenter(client, candidate.node_id, {
|
|
607
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
608
|
+
scrollIntoView: true
|
|
609
|
+
});
|
|
577
610
|
await sleep(350);
|
|
578
611
|
}
|
|
579
612
|
selected.push({
|
|
@@ -638,7 +671,10 @@ export async function setRecruitSchools(client, frameNodeId, schools = []) {
|
|
|
638
671
|
|
|
639
672
|
let box = null;
|
|
640
673
|
if (!clickableActive) {
|
|
641
|
-
box = await clickNodeCenter(client, clickable.node_id, {
|
|
674
|
+
box = await clickNodeCenter(client, clickable.node_id, {
|
|
675
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
676
|
+
scrollIntoView: true
|
|
677
|
+
});
|
|
642
678
|
await sleep(350);
|
|
643
679
|
}
|
|
644
680
|
|
|
@@ -683,7 +719,10 @@ export async function setRecruitRecentViewedFilter(client, frameNodeId, enabled)
|
|
|
683
719
|
|
|
684
720
|
let box = null;
|
|
685
721
|
if (candidate.active !== enabled) {
|
|
686
|
-
box = await clickNodeCenter(client, candidate.node_id, {
|
|
722
|
+
box = await clickNodeCenter(client, candidate.node_id, {
|
|
723
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
724
|
+
scrollIntoView: true
|
|
725
|
+
});
|
|
687
726
|
await sleep(900);
|
|
688
727
|
}
|
|
689
728
|
|
|
@@ -722,7 +761,10 @@ async function selectRecruitNationalCityThroughPicker(client, frameNodeId, {
|
|
|
722
761
|
{ match: "exact", timeoutMs: Math.min(optionTimeoutMs, 6000) }
|
|
723
762
|
);
|
|
724
763
|
if (categoryLookup.candidate) {
|
|
725
|
-
const box = await clickNodeCenter(client, categoryLookup.candidate.node_id, {
|
|
764
|
+
const box = await clickNodeCenter(client, categoryLookup.candidate.node_id, {
|
|
765
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
766
|
+
scrollIntoView: true
|
|
767
|
+
});
|
|
726
768
|
await sleep(400);
|
|
727
769
|
path.push({
|
|
728
770
|
label: "城市",
|
|
@@ -765,7 +807,10 @@ async function selectRecruitNationalCityThroughPicker(client, frameNodeId, {
|
|
|
765
807
|
discovered_options: summarizeTextCandidates(popularLookup.candidates)
|
|
766
808
|
};
|
|
767
809
|
}
|
|
768
|
-
const popularBox = await clickNodeCenter(client, popularLookup.candidate.node_id, {
|
|
810
|
+
const popularBox = await clickNodeCenter(client, popularLookup.candidate.node_id, {
|
|
811
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
812
|
+
scrollIntoView: true
|
|
813
|
+
});
|
|
769
814
|
await sleep(400);
|
|
770
815
|
path.push({
|
|
771
816
|
label: "热门",
|
|
@@ -801,7 +846,10 @@ async function selectRecruitNationalCityThroughPicker(client, frameNodeId, {
|
|
|
801
846
|
};
|
|
802
847
|
}
|
|
803
848
|
|
|
804
|
-
const nationalBox = await clickNodeCenter(client, nationalLookup.candidate.node_id, {
|
|
849
|
+
const nationalBox = await clickNodeCenter(client, nationalLookup.candidate.node_id, {
|
|
850
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
851
|
+
scrollIntoView: true
|
|
852
|
+
});
|
|
805
853
|
await sleep(700);
|
|
806
854
|
path.push({
|
|
807
855
|
label: "全国",
|
|
@@ -953,7 +1001,10 @@ export async function setRecruitCity(client, frameNodeId, city, {
|
|
|
953
1001
|
};
|
|
954
1002
|
}
|
|
955
1003
|
|
|
956
|
-
const box = await clickNodeCenter(client, candidate.node_id, {
|
|
1004
|
+
const box = await clickNodeCenter(client, candidate.node_id, {
|
|
1005
|
+
...DETERMINISTIC_CLICK_OPTIONS,
|
|
1006
|
+
scrollIntoView: true
|
|
1007
|
+
});
|
|
957
1008
|
await sleep(600);
|
|
958
1009
|
return {
|
|
959
1010
|
applied: true,
|
|
@@ -1057,6 +1108,7 @@ export async function applyRecruitSearchParams(client, {
|
|
|
1057
1108
|
if (!controls.ok) {
|
|
1058
1109
|
throw new Error(`Recruit search controls were not ready after navigation; counts=${JSON.stringify(controls.counts || {})}`);
|
|
1059
1110
|
}
|
|
1111
|
+
const overlayDismissal = await dismissRecruitSearchOverlays(client);
|
|
1060
1112
|
const initialRoots = await getRecruitRoots(client);
|
|
1061
1113
|
let frameNodeId = initialRoots.iframe.documentNodeId;
|
|
1062
1114
|
const initialFrameNodeId = frameNodeId;
|
|
@@ -1137,6 +1189,7 @@ export async function applyRecruitSearchParams(client, {
|
|
|
1137
1189
|
applied: true,
|
|
1138
1190
|
search_params: normalizedSearchParams,
|
|
1139
1191
|
reset,
|
|
1192
|
+
overlay_dismissal: overlayDismissal,
|
|
1140
1193
|
controls,
|
|
1141
1194
|
initial_iframe: {
|
|
1142
1195
|
selector: initialRoots.iframe.selector,
|