@reconcrap/boss-recommend-mcp 2.1.14 → 2.1.16

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.
@@ -68,10 +68,11 @@ import {
68
68
  refreshRecommendListAtEnd
69
69
  } from "./refresh.js";
70
70
  import { selectRecommendJob } from "./jobs.js";
71
- import {
72
- normalizeRecommendPageScope,
73
- selectRecommendPageScope
74
- } from "./scopes.js";
71
+ import {
72
+ normalizeRecommendPageScope,
73
+ selectRecommendPageScope
74
+ } from "./scopes.js";
75
+ import { inspectRecentColleagueContact } from "./colleague-contact.js";
75
76
  import {
76
77
  RECOMMEND_BOTTOM_MARKER_SELECTORS,
77
78
  RECOMMEND_CARD_SELECTOR,
@@ -224,19 +225,20 @@ function compactCandidate(candidate) {
224
225
  };
225
226
  }
226
227
 
227
- function compactDetail(detailResult) {
228
- if (!detailResult) return null;
229
- return {
230
- popup_text_length: detailResult.detail?.popup_text?.length || 0,
231
- resume_text_length: detailResult.detail?.resume_text?.length || 0,
228
+ function compactDetail(detailResult) {
229
+ if (!detailResult) return null;
230
+ return {
231
+ popup_text_length: detailResult.detail?.popup_text?.length || 0,
232
+ resume_text_length: detailResult.detail?.resume_text?.length || 0,
232
233
  network_body_count: detailResult.network_bodies?.filter((item) => item.body).length || 0,
233
234
  parsed_network_profile_count: detailResult.parsed_network_profiles?.filter((item) => item.ok).length || 0,
234
- cv_acquisition: detailResult.cv_acquisition || null,
235
- image_evidence: summarizeImageEvidence(detailResult.image_evidence),
236
- llm_screening: compactScreeningLlmResult(detailResult.llm_result),
237
- close_result: detailResult.close_result
238
- };
239
- }
235
+ cv_acquisition: detailResult.cv_acquisition || null,
236
+ colleague_contact: detailResult.colleague_contact || null,
237
+ image_evidence: summarizeImageEvidence(detailResult.image_evidence),
238
+ llm_screening: compactScreeningLlmResult(detailResult.llm_result),
239
+ close_result: detailResult.close_result
240
+ };
241
+ }
240
242
 
241
243
  function normalizeScreeningMode(value) {
242
244
  const normalized = String(value || "llm").trim().toLowerCase();
@@ -427,18 +429,19 @@ function compactRefreshAttempt(refreshAttempt) {
427
429
  };
428
430
  }
429
431
 
430
- export function countRecommendResultStatuses(results = [], {
431
- greetCount = 0
432
- } = {}) {
433
- return {
434
- processed: results.length,
435
- screened: results.length,
436
- detail_opened: results.filter((item) => item.detail).length,
437
- passed: results.filter((item) => item.screening?.passed).length,
438
- llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
439
- greet_count: greetCount,
440
- post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
441
- image_capture_failed: results.filter((item) => item.detail?.image_evidence?.ok === false).length,
432
+ export function countRecommendResultStatuses(results = [], {
433
+ greetCount = 0
434
+ } = {}) {
435
+ return {
436
+ processed: results.length,
437
+ screened: results.length,
438
+ detail_opened: results.filter((item) => item.detail).length,
439
+ passed: results.filter((item) => item.screening?.passed).length,
440
+ skipped: results.filter((item) => item.screening?.passed === false).length,
441
+ llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
442
+ greet_count: greetCount,
443
+ post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
444
+ image_capture_failed: results.filter((item) => item.detail?.image_evidence?.ok === false).length,
442
445
  detail_open_failed: results.filter((item) => (
443
446
  item.error?.code === "DETAIL_STALE_NODE"
444
447
  || item.error?.code === "DETAIL_OPEN_FAILED"
@@ -449,9 +452,17 @@ export function countRecommendResultStatuses(results = [], {
449
452
  || item.error?.code === "IMAGE_CAPTURE_STALE_NODE"
450
453
  || item.error?.code === "IMAGE_CAPTURE_TIMEOUT"
451
454
  || item.error?.code === "IMAGE_CAPTURE_TOTAL_TIMEOUT"
452
- )).length
453
- };
454
- }
455
+ )).length,
456
+ colleague_contact_checked: results.filter((item) => item.detail?.colleague_contact?.checked).length,
457
+ recent_colleague_contact_skipped: results.filter((item) => (
458
+ item.screening?.status === "skip"
459
+ && item.screening?.reasons?.includes("skipped_recent_colleague_contact")
460
+ )).length,
461
+ colleague_contact_panel_missing: results.filter((item) => (
462
+ item.detail?.colleague_contact?.reason === "panel_missing"
463
+ )).length
464
+ };
465
+ }
455
466
 
456
467
  function countPassedResults(results = []) {
457
468
  return countRecommendResultStatuses(results).passed;
@@ -612,10 +623,10 @@ function compactRecoverableDetailError(error) {
612
623
  return compactError(error, isStaleRecommendNodeError(error) ? "DETAIL_STALE_NODE" : "DETAIL_OPEN_FAILED");
613
624
  }
614
625
 
615
- function createRecoverableDetailFailureScreening(candidate, error) {
616
- return {
617
- status: "fail",
618
- passed: false,
626
+ function createRecoverableDetailFailureScreening(candidate, error) {
627
+ return {
628
+ status: "fail",
629
+ passed: false,
619
630
  score: 0,
620
631
  reasons: isStaleRecommendNodeError(error)
621
632
  ? ["detail_open_failed", "stale_node"]
@@ -623,9 +634,22 @@ function createRecoverableDetailFailureScreening(candidate, error) {
623
634
  ? ["detail_open_failed", "detail_open_miss"]
624
635
  : ["detail_open_failed"],
625
636
  error: compactRecoverableDetailError(error),
626
- candidate
627
- };
628
- }
637
+ candidate
638
+ };
639
+ }
640
+
641
+ function createRecentColleagueContactSkipScreening(candidate, colleagueContact) {
642
+ const matched = colleagueContact?.matched_row || null;
643
+ return {
644
+ status: "skip",
645
+ passed: false,
646
+ score: 0,
647
+ reasons: ["skipped_recent_colleague_contact"],
648
+ reason: matched?.text || "Candidate has recent colleague contact history",
649
+ matched_colleague_contact: matched,
650
+ candidate
651
+ };
652
+ }
629
653
 
630
654
  export async function runRecommendWorkflow({
631
655
  client,
@@ -657,15 +681,17 @@ export async function runRecommendWorkflow({
657
681
  executePostAction = true,
658
682
  actionTimeoutMs = 8000,
659
683
  actionIntervalMs = 500,
660
- actionAfterClickDelayMs = 900,
661
- screeningMode = "llm",
662
- llmConfig = null,
684
+ actionAfterClickDelayMs = 900,
685
+ screeningMode = "llm",
686
+ llmConfig = null,
663
687
  llmTimeoutMs = 120000,
664
688
  llmImageLimit = 8,
665
689
  llmImageDetail = "high",
666
690
  imageOutputDir = "",
667
691
  humanRestEnabled = false,
668
- humanBehavior = null
692
+ humanBehavior = null,
693
+ skipRecentColleagueContacted = true,
694
+ colleagueContactWindowDays = 14
669
695
  } = {}, runControl) {
670
696
  if (!client) throw new Error("runRecommendWorkflow requires a guarded CDP client");
671
697
  const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
@@ -689,10 +715,13 @@ export async function runRecommendWorkflow({
689
715
  const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
690
716
  const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
691
717
  const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
692
- const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
693
- const useLlmScreening = normalizedScreeningMode !== "deterministic";
694
- const postActionEnabled = normalizedPostAction !== "none";
695
- const targetPassCount = Math.max(1, Number(maxCandidates) || 1);
718
+ const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
719
+ const useLlmScreening = normalizedScreeningMode !== "deterministic";
720
+ const postActionEnabled = normalizedPostAction !== "none";
721
+ const shouldSkipRecentColleagueContacted = skipRecentColleagueContacted !== false;
722
+ const normalizedColleagueContactWindowDays = Math.max(1, Number(colleagueContactWindowDays) || 14);
723
+ const colleagueContactReferenceDate = new Date();
724
+ const targetPassCount = Math.max(1, Number(maxCandidates) || 1);
696
725
  const detailCountLimit = detailLimit == null ? Number.POSITIVE_INFINITY : Math.max(0, Number(detailLimit) || 0);
697
726
  const effectiveDetailLimit = postActionEnabled ? Number.POSITIVE_INFINITY : detailCountLimit;
698
727
  const networkRecorder = effectiveDetailLimit > 0
@@ -785,6 +814,8 @@ export async function runRecommendWorkflow({
785
814
  human_behavior_profile: effectiveHumanBehavior.profile,
786
815
  human_rest_level: effectiveHumanBehavior.restLevel,
787
816
  human_rest_enabled: effectiveHumanRestEnabled,
817
+ skip_recent_colleague_contacted: shouldSkipRecentColleagueContacted,
818
+ colleague_contact_window_days: normalizedColleagueContactWindowDays,
788
819
  human_rest_count: humanRestState.rest_count,
789
820
  human_rest_ms: humanRestState.total_rest_ms,
790
821
  last_human_event: lastHumanEvent,
@@ -1109,9 +1140,11 @@ export async function runRecommendWorkflow({
1109
1140
  let cardCandidate = nextCandidateResult.item.candidate;
1110
1141
 
1111
1142
  let screeningCandidate = cardCandidate;
1112
- let detailResult = null;
1113
- let recoverableDetailError = null;
1114
- let detailStep = "not_started";
1143
+ let detailResult = null;
1144
+ let recoverableDetailError = null;
1145
+ let colleagueContact = null;
1146
+ let skipRecentColleagueContact = false;
1147
+ let detailStep = "not_started";
1115
1148
  if (index < effectiveDetailLimit) {
1116
1149
  try {
1117
1150
  await runControl.waitIfPaused();
@@ -1153,12 +1186,52 @@ export async function runRecommendWorkflow({
1153
1186
  });
1154
1187
  addTiming(timings, "candidate_click_ms", openedDetail.timings?.candidate_click_ms);
1155
1188
  addTiming(timings, "detail_open_ms", openedDetail.timings?.detail_open_ms);
1156
- cardNodeId = openedDetail.card_node_id || cardNodeId;
1157
- cardCandidate = openedDetail.card_candidate || cardCandidate;
1158
- screeningCandidate = cardCandidate;
1159
- const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
1160
- detailStep = "wait_network";
1161
- const networkWait = await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
1189
+ cardNodeId = openedDetail.card_node_id || cardNodeId;
1190
+ cardCandidate = openedDetail.card_candidate || cardCandidate;
1191
+ screeningCandidate = cardCandidate;
1192
+ if (shouldSkipRecentColleagueContacted) {
1193
+ detailStep = "check_colleague_contact";
1194
+ try {
1195
+ colleagueContact = await measureTiming(timings, "colleague_contact_check_ms", () => inspectRecentColleagueContact(
1196
+ client,
1197
+ openedDetail.detail_state,
1198
+ {
1199
+ referenceDate: colleagueContactReferenceDate,
1200
+ windowDays: normalizedColleagueContactWindowDays
1201
+ }
1202
+ ));
1203
+ if (colleagueContact?.recent) {
1204
+ skipRecentColleagueContact = true;
1205
+ detailResult = {
1206
+ candidate: screeningCandidate,
1207
+ detail: {
1208
+ popup_text: "",
1209
+ resume_text: ""
1210
+ },
1211
+ colleague_contact: colleagueContact,
1212
+ cv_acquisition: {
1213
+ source: "skipped_recent_colleague_contact",
1214
+ skipped: true,
1215
+ reason: "skipped_recent_colleague_contact"
1216
+ }
1217
+ };
1218
+ detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecommendDetail(client));
1219
+ await maybeHumanActionCooldown("after_detail_close", timings);
1220
+ }
1221
+ } catch (error) {
1222
+ colleagueContact = {
1223
+ checked: false,
1224
+ recent: false,
1225
+ reason: "inspection_failed",
1226
+ error: error?.message || String(error),
1227
+ window_days: normalizedColleagueContactWindowDays
1228
+ };
1229
+ }
1230
+ }
1231
+ if (!skipRecentColleagueContact) {
1232
+ const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
1233
+ detailStep = "wait_network";
1234
+ const networkWait = await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
1162
1235
  waitForRecommendDetailNetworkEvents,
1163
1236
  networkRecorder,
1164
1237
  {
@@ -1182,9 +1255,10 @@ export async function runRecommendWorkflow({
1182
1255
  networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
1183
1256
  networkParseIntervalMs: 250
1184
1257
  });
1185
- addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
1186
-
1187
- const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
1258
+ addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
1259
+ if (colleagueContact) detailResult.colleague_contact = colleagueContact;
1260
+
1261
+ const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
1188
1262
  let source = "network";
1189
1263
  let imageEvidence = null;
1190
1264
  let captureTarget = null;
@@ -1296,9 +1370,10 @@ export async function runRecommendWorkflow({
1296
1370
  image_evidence: summarizeImageEvidence(imageEvidence),
1297
1371
  capture_target: captureTarget || null,
1298
1372
  capture_target_wait: captureTargetWait
1299
- };
1300
- screeningCandidate = detailResult.candidate;
1301
- } catch (error) {
1373
+ };
1374
+ screeningCandidate = detailResult.candidate;
1375
+ }
1376
+ } catch (error) {
1302
1377
  if (!isRecoverableRecommendDetailError(error)) throw error;
1303
1378
  const recoveryCount = candidateRecoveryCounts.get(candidateKey) || 0;
1304
1379
  if (recoveryCount < 1) {
@@ -1325,11 +1400,11 @@ export async function runRecommendWorkflow({
1325
1400
  await runControl.waitIfPaused();
1326
1401
  runControl.throwIfCanceled();
1327
1402
  runControl.setPhase("recommend:screening");
1328
- let llmResult = null;
1329
- if (useLlmScreening) {
1330
- if (recoverableDetailError || detailResult?.image_evidence?.ok === false) {
1331
- llmResult = null;
1332
- } else if (!llmConfig) {
1403
+ let llmResult = null;
1404
+ if (useLlmScreening) {
1405
+ if (skipRecentColleagueContact || recoverableDetailError || detailResult?.image_evidence?.ok === false) {
1406
+ llmResult = null;
1407
+ } else if (!llmConfig) {
1333
1408
  llmResult = createMissingLlmConfigResult();
1334
1409
  } else {
1335
1410
  try {
@@ -1354,12 +1429,14 @@ export async function runRecommendWorkflow({
1354
1429
  }
1355
1430
  llmResult = createFailedLlmScreeningResult(error);
1356
1431
  }
1357
- }
1358
- if (detailResult) detailResult.llm_result = llmResult;
1359
- }
1360
- const screening = recoverableDetailError
1361
- ? createRecoverableDetailFailureScreening(screeningCandidate, recoverableDetailError)
1362
- : detailResult?.image_evidence?.ok === false
1432
+ }
1433
+ if (detailResult) detailResult.llm_result = llmResult;
1434
+ }
1435
+ const screening = skipRecentColleagueContact
1436
+ ? createRecentColleagueContactSkipScreening(screeningCandidate, colleagueContact)
1437
+ : recoverableDetailError
1438
+ ? createRecoverableDetailFailureScreening(screeningCandidate, recoverableDetailError)
1439
+ : detailResult?.image_evidence?.ok === false
1363
1440
  ? createImageCaptureFailureScreening(screeningCandidate, {
1364
1441
  code: detailResult.image_evidence.error_code,
1365
1442
  message: detailResult.image_evidence.error
@@ -1371,7 +1448,7 @@ export async function runRecommendWorkflow({
1371
1448
  let postActionResult = null;
1372
1449
  let closeFailureError = null;
1373
1450
  let closeRecoveryFailure = null;
1374
- if (postActionEnabled && detailResult) {
1451
+ if (postActionEnabled && detailResult && !skipRecentColleagueContact) {
1375
1452
  const postActionStarted = Date.now();
1376
1453
  await runControl.waitIfPaused();
1377
1454
  runControl.throwIfCanceled();
@@ -1397,7 +1474,7 @@ export async function runRecommendWorkflow({
1397
1474
  }
1398
1475
  addTiming(timings, "post_action_ms", Date.now() - postActionStarted);
1399
1476
  }
1400
- if (detailResult && closeDetail) {
1477
+ if (detailResult && closeDetail && !detailResult.close_result?.closed) {
1401
1478
  detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecommendDetail(client));
1402
1479
  await maybeHumanActionCooldown("after_detail_close", timings);
1403
1480
  if (!detailResult.close_result?.closed) {
@@ -1597,6 +1674,8 @@ export function createRecommendRunService({
1597
1674
  imageOutputDir = "",
1598
1675
  humanRestEnabled = false,
1599
1676
  humanBehavior = null,
1677
+ skipRecentColleagueContacted = true,
1678
+ colleagueContactWindowDays = 14,
1600
1679
  name = "recommend-domain-run"
1601
1680
  } = {}) {
1602
1681
  if (!client) throw new Error("startRecommendRun requires a guarded CDP client");
@@ -1605,6 +1684,8 @@ export function createRecommendRunService({
1605
1684
  const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
1606
1685
  const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
1607
1686
  const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
1687
+ const shouldSkipRecentColleagueContacted = skipRecentColleagueContacted !== false;
1688
+ const normalizedColleagueContactWindowDays = Math.max(1, Number(colleagueContactWindowDays) || 14);
1608
1689
  const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
1609
1690
  legacyEnabled: humanRestEnabled === true || llmConfig?.humanRestEnabled === true
1610
1691
  });
@@ -1649,6 +1730,8 @@ export function createRecommendRunService({
1649
1730
  llm_image_limit: llmImageLimit,
1650
1731
  llm_image_detail: llmImageDetail,
1651
1732
  image_output_dir: imageOutputDir || "",
1733
+ skip_recent_colleague_contacted: shouldSkipRecentColleagueContacted,
1734
+ colleague_contact_window_days: normalizedColleagueContactWindowDays,
1652
1735
  human_behavior_enabled: effectiveHumanBehavior.enabled,
1653
1736
  human_behavior_profile: effectiveHumanBehavior.profile,
1654
1737
  human_behavior: effectiveHumanBehavior,
@@ -1662,13 +1745,17 @@ export function createRecommendRunService({
1662
1745
  processed: 0,
1663
1746
  screened: 0,
1664
1747
  detail_opened: 0,
1665
- llm_screened: 0,
1666
- passed: 0,
1667
- greet_count: 0,
1748
+ llm_screened: 0,
1749
+ passed: 0,
1750
+ skipped: 0,
1751
+ greet_count: 0,
1668
1752
  post_action_clicked: 0,
1669
1753
  image_capture_failed: 0,
1670
1754
  detail_open_failed: 0,
1671
1755
  transient_recovered: 0,
1756
+ colleague_contact_checked: 0,
1757
+ recent_colleague_contact_skipped: 0,
1758
+ colleague_contact_panel_missing: 0,
1672
1759
  context_recoveries: 0,
1673
1760
  human_behavior_enabled: effectiveHumanBehavior.enabled,
1674
1761
  human_behavior_profile: effectiveHumanBehavior.profile,
@@ -1717,7 +1804,9 @@ export function createRecommendRunService({
1717
1804
  llmImageDetail,
1718
1805
  imageOutputDir,
1719
1806
  humanRestEnabled: effectiveHumanRestEnabled,
1720
- humanBehavior: effectiveHumanBehavior
1807
+ humanBehavior: effectiveHumanBehavior,
1808
+ skipRecentColleagueContacted: shouldSkipRecentColleagueContacted,
1809
+ colleagueContactWindowDays: normalizedColleagueContactWindowDays
1721
1810
  }, runControl)
1722
1811
  });
1723
1812
  }
@@ -75,6 +75,12 @@ export const RECRUIT_SEARCH_SELECTORS = Object.freeze({
75
75
  'button[ka*="search"]',
76
76
  '[class*="search"][class*="btn"]'
77
77
  ],
78
+ jobTitleTrigger: [
79
+ ".search-job-list-C .ui-dropmenu",
80
+ ".search-job-list-C .ui-dropmenu-label",
81
+ ".search-job-list-C .search-current-job",
82
+ ".search-job-list-C"
83
+ ],
78
84
  jobTitleOption: [
79
85
  '.search-job-list-C li[ka="search_select_job"]',
80
86
  ".search-job-list-C li",
@@ -104,6 +110,69 @@ export const RECRUIT_SEARCH_SELECTORS = Object.freeze({
104
110
  "label.checkbox",
105
111
  '[ka="search_change_view_resume"]'
106
112
  ],
113
+ exchangeResumeLabel: [
114
+ 'label.checkbox.high_search_checkbox[ka="search_change_exchange_resume"]',
115
+ '[ka="search_change_exchange_resume"]'
116
+ ],
117
+ experienceOption: [
118
+ ".experience-select .exp-item",
119
+ ".experience-select li",
120
+ ".exp-list-ui .exp-item",
121
+ '[class*="experience"] [class*="exp-item"]'
122
+ ],
123
+ experienceCustom: [
124
+ ".experience-select .custom",
125
+ ".experience-select .custom-wrap",
126
+ ".experience-select-custom-slider",
127
+ '[class*="experience"] [class*="custom"]'
128
+ ],
129
+ experienceCustomSlider: [
130
+ ".experience-select .experience-select-custom-slider .ui-slider-wrap",
131
+ ".experience-select .ui-slider-wrap",
132
+ ".experience-select-custom-slider .ui-slider-wrap"
133
+ ],
134
+ experienceCustomSliderHandle: [
135
+ ".experience-select .experience-select-custom-slider .ui-slider-button-wrap",
136
+ ".experience-select .ui-slider-button-wrap",
137
+ ".experience-select-custom-slider .ui-slider-button-wrap"
138
+ ],
139
+ experienceCustomHiddenInput: [
140
+ ".experience-select .experience-select-custom-slider input[type='hidden']",
141
+ ".experience-select input[type='hidden']",
142
+ ".experience-select-custom-slider input[type='hidden']"
143
+ ],
144
+ ageOption: [
145
+ ".age-select .age-item",
146
+ ".age-list-ui .age-item",
147
+ '[class*="age"] [class*="age-item"]'
148
+ ],
149
+ ageCustom: [
150
+ ".age-select .custom",
151
+ ".age-select .age-custom",
152
+ '[class*="age"] [class*="custom"]'
153
+ ],
154
+ ageCustomDropdown: [
155
+ ".age-select .age-custom .dropdown-wrap.select"
156
+ ],
157
+ ageCustomOption: [
158
+ ".age-select .age-custom li",
159
+ ".age-select .age-custom .dropdown-menu li"
160
+ ],
161
+ ageCustomInput: [
162
+ ".age-select .age-custom input"
163
+ ],
164
+ genderDropdown: [
165
+ ".gender-select"
166
+ ],
167
+ genderOption: [
168
+ ".gender-select.dropdown-menu-open li",
169
+ ".gender-select .dropdown-menu li"
170
+ ],
171
+ cityTrigger: [
172
+ ".city-wrap .city",
173
+ ".city-wrap",
174
+ ".search-wrap .city-wrap"
175
+ ],
107
176
  cityInput: [
108
177
  ".city-wrap .search-city-kw input",
109
178
  ".search-city-kw input",