mobile-mcp-ai 2.7.3__py3-none-any.whl → 2.7.5__py3-none-any.whl

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.
@@ -1313,13 +1313,13 @@ class BasicMobileToolsLite:
1313
1313
  page_texts = self._get_page_texts(10)
1314
1314
  return {"success": True, "page_texts": page_texts}
1315
1315
 
1316
- # 选择器失败,用坐标兜底
1316
+ # 选择器失败,用控件中心坐标点兜底
1317
1317
  if bounds:
1318
1318
  x = (bounds[0] + bounds[2]) // 2
1319
1319
  y = (bounds[1] + bounds[3]) // 2
1320
1320
  self.client.u2.click(x, y)
1321
1321
  time.sleep(0.3)
1322
- self._record_click('percent', f"{x_pct}%,{y_pct}%", x_pct, y_pct,
1322
+ self._record_click('coords', f"{x},{y}", x_pct, y_pct,
1323
1323
  element_desc=text)
1324
1324
  # 验证逻辑
1325
1325
  if verify:
@@ -1373,12 +1373,14 @@ class BasicMobileToolsLite:
1373
1373
  except Exception as e:
1374
1374
  return {"success": True, "verified": False, "hint": f"验证异常: {e}"}
1375
1375
 
1376
- def _find_element_in_tree(self, text: str, position: Optional[str] = None) -> Optional[Dict]:
1377
- """在 XML 树中查找包含指定文本的元素,优先返回可点击的元素
1376
+ def _find_element_in_tree(self, text: str, position: Optional[str] = None, exact_match: bool = True) -> Optional[Dict]:
1377
+ """在 XML 树中查找指定文本的元素,优先返回可点击的元素
1378
1378
 
1379
1379
  Args:
1380
1380
  text: 要查找的文本
1381
1381
  position: 位置信息,用于在有多个相同文案时筛选
1382
+ exact_match: 是否精确匹配。True=优先精确匹配(用于定位元素如点击),
1383
+ False=只进行包含匹配(用于验证元素)
1382
1384
  """
1383
1385
  try:
1384
1386
  xml = self.client.u2.dump_hierarchy(compressed=False)
@@ -1410,26 +1412,40 @@ class BasicMobileToolsLite:
1410
1412
  attr_type = None
1411
1413
  attr_value = None
1412
1414
 
1413
- # 精确匹配 text
1414
- if elem_text == text:
1415
- is_match = True
1416
- attr_type = 'text'
1417
- attr_value = text
1418
- # 精确匹配 content-desc
1419
- elif elem_desc == text:
1420
- is_match = True
1421
- attr_type = 'description'
1422
- attr_value = text
1423
- # 模糊匹配 text
1424
- elif text in elem_text:
1425
- is_match = True
1426
- attr_type = 'textContains'
1427
- attr_value = text
1428
- # 模糊匹配 content-desc
1429
- elif text in elem_desc:
1430
- is_match = True
1431
- attr_type = 'descriptionContains'
1432
- attr_value = text
1415
+ if exact_match:
1416
+ # 精确匹配模式(用于定位元素):优先精确匹配
1417
+ # 精确匹配 text
1418
+ if elem_text == text:
1419
+ is_match = True
1420
+ attr_type = 'text'
1421
+ attr_value = text
1422
+ # 精确匹配 content-desc
1423
+ elif elem_desc == text:
1424
+ is_match = True
1425
+ attr_type = 'description'
1426
+ attr_value = text
1427
+ # 精确匹配找不到时,再尝试包含匹配(作为兜底)
1428
+ elif text in elem_text:
1429
+ is_match = True
1430
+ attr_type = 'textContains'
1431
+ attr_value = text
1432
+ # 包含匹配 content-desc
1433
+ elif text in elem_desc:
1434
+ is_match = True
1435
+ attr_type = 'descriptionContains'
1436
+ attr_value = text
1437
+ else:
1438
+ # 包含匹配模式(用于验证元素):只进行包含匹配
1439
+ # 包含匹配 text
1440
+ if text in elem_text:
1441
+ is_match = True
1442
+ attr_type = 'textContains'
1443
+ attr_value = text
1444
+ # 包含匹配 content-desc
1445
+ elif text in elem_desc:
1446
+ is_match = True
1447
+ attr_type = 'descriptionContains'
1448
+ attr_value = text
1433
1449
 
1434
1450
  if is_match and bounds:
1435
1451
  # 计算元素的中心点坐标
@@ -1448,6 +1464,17 @@ class BasicMobileToolsLite:
1448
1464
  if not matched_elements:
1449
1465
  return None
1450
1466
 
1467
+ # 精确匹配模式下,优先返回精确匹配的元素(text/description),再返回包含匹配的元素
1468
+ if exact_match:
1469
+ exact_matches = [m for m in matched_elements if m['attr_type'] in ['text', 'description']]
1470
+ contains_matches = [m for m in matched_elements if m['attr_type'] in ['textContains', 'descriptionContains']]
1471
+ # 如果有精确匹配,优先使用精确匹配的结果
1472
+ if exact_matches:
1473
+ matched_elements = exact_matches + contains_matches
1474
+ # 如果没有精确匹配,使用包含匹配的结果
1475
+ else:
1476
+ matched_elements = contains_matches
1477
+
1451
1478
  # 如果有位置信息,根据位置筛选
1452
1479
  if position and len(matched_elements) > 1:
1453
1480
  position_lower = position.lower()
@@ -1486,6 +1513,7 @@ class BasicMobileToolsLite:
1486
1513
  }
1487
1514
 
1488
1515
  # 没有位置信息时,优先返回可点击的元素
1516
+ # 由于前面已经排序(精确匹配在前),这里会优先返回精确匹配且可点击的元素
1489
1517
  for match in matched_elements:
1490
1518
  if match['clickable']:
1491
1519
  return {
@@ -3300,7 +3328,7 @@ class BasicMobileToolsLite:
3300
3328
  pass
3301
3329
 
3302
3330
  if not close_candidates:
3303
- # 兜底策略:如果检测到弹窗但未找到关闭按钮,且页面元素很少(只有1个可点击元素),直接点击它
3331
+ # 兜底策略1:如果检测到弹窗但未找到关闭按钮,且页面元素很少(只有1个可点击元素),直接点击它
3304
3332
  if popup_detected and popup_bounds and len(all_clickable_elements) == 1:
3305
3333
  single_element = all_clickable_elements[0]
3306
3334
  self.client.u2.click(single_element['center_x'], single_element['center_y'])
@@ -3326,6 +3354,37 @@ class BasicMobileToolsLite:
3326
3354
  result["returned"] = return_result['success']
3327
3355
  return result
3328
3356
 
3357
+ # 兜底策略2:即使未检测到弹窗,如果页面只有一个可点击元素,也尝试点击它(可能是特殊类型的弹窗)
3358
+ # 这种情况通常出现在:下载浮层、特殊弹窗等,它们的 resource-id 可能不包含 dialog/popup 等关键词
3359
+ if len(all_clickable_elements) == 1:
3360
+ single_element = all_clickable_elements[0]
3361
+ # 检查元素是否占据较大屏幕区域(可能是弹窗)
3362
+ element_area_ratio = (single_element['width'] * single_element['height']) / (screen_width * screen_height)
3363
+ # 如果元素占据屏幕 20% 以上,认为是可能的弹窗
3364
+ if element_area_ratio > 0.2:
3365
+ self.client.u2.click(single_element['center_x'], single_element['center_y'])
3366
+ time.sleep(0.5)
3367
+
3368
+ # 检查应用是否跳转
3369
+ app_check = self._check_app_switched()
3370
+ return_result = None
3371
+ if app_check['switched']:
3372
+ return_result = self._return_to_target_app()
3373
+
3374
+ # 记录操作
3375
+ rel_x = single_element['center_x'] / screen_width
3376
+ rel_y = single_element['center_y'] / screen_height
3377
+ self._record_click('percent', f"{round(rel_x * 100, 1)}%,{round(rel_y * 100, 1)}%",
3378
+ round(rel_x * 100, 1), round(rel_y * 100, 1),
3379
+ element_desc="唯一可点击元素(特殊弹窗兜底)")
3380
+
3381
+ result = {"success": True, "clicked": True, "method": "single_clickable_special_popup_fallback"}
3382
+ if app_check['switched']:
3383
+ result["switched"] = True
3384
+ if return_result:
3385
+ result["returned"] = return_result['success']
3386
+ return result
3387
+
3329
3388
  # 如果没有找到关闭按钮,且不满足兜底条件,返回fallback
3330
3389
  if popup_detected and popup_bounds:
3331
3390
  return {"success": False, "fallback": "vision", "popup": True}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mobile-mcp-ai
3
- Version: 2.7.3
3
+ Version: 2.7.5
4
4
  Summary: 移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)
5
5
  Home-page: https://github.com/test111ddff-hash/mobile-mcp-ai
6
6
  Author: douzi
@@ -1,7 +1,7 @@
1
1
  mobile_mcp/__init__.py,sha256=sQJZTL_sxQFzmcS7jOtS2AHCfUySz40vhX96N6u1qy4,816
2
2
  mobile_mcp/config.py,sha256=-xSl9vahp3EFAA97P1ahcnQC-HHAFvccGHpnFAXeKHU,5841
3
3
  mobile_mcp/core/__init__.py,sha256=ndMy-cLAIsQDG5op7gM_AIplycqZSZPWEkec1pEhvEY,170
4
- mobile_mcp/core/basic_tools_lite.py,sha256=DPLPVoi-e-KsYAHANXKlBqCcJPBaE4lV_Qvf-zjL9Ng,219759
4
+ mobile_mcp/core/basic_tools_lite.py,sha256=XRt_T4sWtUpZZFs4fnZsxEMk8x1F3bdoK-8FK-frf08,223778
5
5
  mobile_mcp/core/device_manager.py,sha256=xG5DoeNFs45pl-FTEhEWblqVwxtFK-FmVEGlNL6EqRI,8798
6
6
  mobile_mcp/core/dynamic_config.py,sha256=Ja1n1pfb0HspGByqk2_A472mYVniKmGtNEWyjUjmgK8,9811
7
7
  mobile_mcp/core/ios_client_wda.py,sha256=Nq9WxevhTWpVpolM-Ymp-b0nUQV3tXLFszmJHbDC4wA,18770
@@ -24,9 +24,9 @@ mobile_mcp/utils/__init__.py,sha256=8EH0i7UGtx1y_j_GEgdN-cZdWn2sRtZSEOLlNF9HRnY,
24
24
  mobile_mcp/utils/logger.py,sha256=Sqq2Nr0Y4p03erqcrbYKVPCGiFaNGHMcE_JwCkeOfU4,3626
25
25
  mobile_mcp/utils/xml_formatter.py,sha256=uwTRb3vLbqhT8O-udzWT7s7LsV-DyDUz2DkofD3hXOE,4556
26
26
  mobile_mcp/utils/xml_parser.py,sha256=QhL8CWbdmNDzmBLjtx6mEnjHgMFZzJeHpCL15qfXSpI,3926
27
- mobile_mcp_ai-2.7.3.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
28
- mobile_mcp_ai-2.7.3.dist-info/METADATA,sha256=60jvdRSHcL4QWPCaeiFxLunyBbT_2wkYF0UIo-Fq4v0,11505
29
- mobile_mcp_ai-2.7.3.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
30
- mobile_mcp_ai-2.7.3.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
31
- mobile_mcp_ai-2.7.3.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
32
- mobile_mcp_ai-2.7.3.dist-info/RECORD,,
27
+ mobile_mcp_ai-2.7.5.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
28
+ mobile_mcp_ai-2.7.5.dist-info/METADATA,sha256=N1FCdAUoa4yJ9Rkbawl7n_kNn7sYPYYkR3GzoTBssEg,11505
29
+ mobile_mcp_ai-2.7.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
30
+ mobile_mcp_ai-2.7.5.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
31
+ mobile_mcp_ai-2.7.5.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
32
+ mobile_mcp_ai-2.7.5.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5