mobile-mcp-ai 2.6.0__tar.gz → 2.6.1__tar.gz
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.
- {mobile_mcp_ai-2.6.0/mobile_mcp_ai.egg-info → mobile_mcp_ai-2.6.1}/PKG-INFO +1 -1
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/basic_tools_lite.py +40 -3
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/mcp_tools/mcp_server.py +22 -17
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1/mobile_mcp_ai.egg-info}/PKG-INFO +1 -1
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/mobile_mcp_ai.egg-info/SOURCES.txt +10 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/setup.py +1 -1
- mobile_mcp_ai-2.6.1/tests/test_mind_cloud_my_space.py +80 -0
- mobile_mcp_ai-2.6.1/tests/test_mind_correct.py +73 -0
- mobile_mcp_ai-2.6.1/tests/test_mind_improved.py +83 -0
- mobile_mcp_ai-2.6.1/tests/test_mind_optimized.py +77 -0
- mobile_mcp_ai-2.6.1/tests/test_open_mind.py +37 -0
- mobile_mcp_ai-2.6.1/tests/test_priority_demo.py +81 -0
- mobile_mcp_ai-2.6.1/tests/test_simple.py +76 -0
- mobile_mcp_ai-2.6.1/tests/test_/344/270/276/346/212/245.py +136 -0
- mobile_mcp_ai-2.6.1/tests/test_/345/210/207/346/215/242/350/257/255/350/250/200/345/210/260English.py +158 -0
- mobile_mcp_ai-2.6.1/tests/test_/346/265/213/350/257/225.py +114 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/LICENSE +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/MANIFEST.in +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/README.md +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/__init__.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/config.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/__init__.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/device_manager.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/dynamic_config.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/ios_client_wda.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/ios_device_manager_wda.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/mobile_client.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/template_matcher.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/utils/__init__.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/utils/logger.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/utils/operation_history_manager.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/utils/smart_wait.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/docs/iOS_SETUP_GUIDE.md +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/mcp_tools/__init__.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/mobile_mcp_ai.egg-info/dependency_links.txt +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/mobile_mcp_ai.egg-info/entry_points.txt +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/mobile_mcp_ai.egg-info/not-zip-safe +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/mobile_mcp_ai.egg-info/requires.txt +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/mobile_mcp_ai.egg-info/top_level.txt +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/requirements.txt +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/setup.cfg +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/templates/close_buttons/auto_x_0112_151217.png +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/templates/close_buttons/auto_x_0112_152037.png +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/templates/close_buttons/auto_x_0112_152840.png +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/templates/close_buttons/auto_x_0112_153256.png +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/templates/close_buttons/auto_x_0112_154847.png +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/templates/close_buttons/gray_x_stock_ad.png +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/utils/__init__.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/utils/logger.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/utils/xml_formatter.py +0 -0
- {mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/utils/xml_parser.py +0 -0
|
@@ -2349,6 +2349,7 @@ class BasicMobileToolsLite:
|
|
|
2349
2349
|
for elem in root.iter():
|
|
2350
2350
|
text = elem.attrib.get('text', '')
|
|
2351
2351
|
content_desc = elem.attrib.get('content-desc', '')
|
|
2352
|
+
resource_id = elem.attrib.get('resource-id', '')
|
|
2352
2353
|
bounds_str = elem.attrib.get('bounds', '')
|
|
2353
2354
|
class_name = elem.attrib.get('class', '')
|
|
2354
2355
|
clickable = elem.attrib.get('clickable', 'false') == 'true'
|
|
@@ -2383,6 +2384,13 @@ class BasicMobileToolsLite:
|
|
|
2383
2384
|
score = 90
|
|
2384
2385
|
reason = f"描述='{content_desc}'"
|
|
2385
2386
|
|
|
2387
|
+
# 策略2.5:resource-id 包含关闭关键词(如 close_icon, ad_close 等)
|
|
2388
|
+
elif resource_id and any(kw in resource_id.lower() for kw in ['close', 'dismiss', 'skip', 'cancel']):
|
|
2389
|
+
score = 95
|
|
2390
|
+
# 提取简短的 id 名
|
|
2391
|
+
short_id = resource_id.split('/')[-1] if '/' in resource_id else resource_id
|
|
2392
|
+
reason = f"resource-id='{short_id}'"
|
|
2393
|
+
|
|
2386
2394
|
# 策略3:小尺寸的 clickable 元素(可能是 X 图标)
|
|
2387
2395
|
elif clickable:
|
|
2388
2396
|
min_size = max(20, int(screen_width * 0.03))
|
|
@@ -2417,7 +2425,9 @@ class BasicMobileToolsLite:
|
|
|
2417
2425
|
'center_y': center_y,
|
|
2418
2426
|
'x_percent': x_percent,
|
|
2419
2427
|
'y_percent': y_percent,
|
|
2420
|
-
'size': f"{width}x{height}"
|
|
2428
|
+
'size': f"{width}x{height}",
|
|
2429
|
+
'resource_id': resource_id,
|
|
2430
|
+
'text': text
|
|
2421
2431
|
})
|
|
2422
2432
|
|
|
2423
2433
|
if not candidates:
|
|
@@ -2443,7 +2453,16 @@ class BasicMobileToolsLite:
|
|
|
2443
2453
|
candidates.sort(key=lambda x: x['score'], reverse=True)
|
|
2444
2454
|
best = candidates[0]
|
|
2445
2455
|
|
|
2446
|
-
|
|
2456
|
+
# 生成推荐的点击命令(优先使用 resource-id)
|
|
2457
|
+
if best.get('resource_id'):
|
|
2458
|
+
short_id = best['resource_id'].split('/')[-1] if '/' in best['resource_id'] else best['resource_id']
|
|
2459
|
+
click_cmd = f"mobile_click_by_id('{best['resource_id']}')"
|
|
2460
|
+
elif best.get('text') and best['text'] in ['×', 'X', 'x', '关闭', '取消', '跳过', '知道了']:
|
|
2461
|
+
click_cmd = f"mobile_click_by_text('{best['text']}')"
|
|
2462
|
+
else:
|
|
2463
|
+
click_cmd = f"mobile_click_by_percent({best['x_percent']}, {best['y_percent']})"
|
|
2464
|
+
|
|
2465
|
+
result = {
|
|
2447
2466
|
"success": True,
|
|
2448
2467
|
"message": f"✅ 找到可能的关闭按钮",
|
|
2449
2468
|
"best_candidate": {
|
|
@@ -2454,7 +2473,7 @@ class BasicMobileToolsLite:
|
|
|
2454
2473
|
"size": best['size'],
|
|
2455
2474
|
"score": best['score']
|
|
2456
2475
|
},
|
|
2457
|
-
"click_command":
|
|
2476
|
+
"click_command": click_cmd,
|
|
2458
2477
|
"other_candidates": [
|
|
2459
2478
|
{"reason": c['reason'], "percent": f"({c['x_percent']}%, {c['y_percent']}%)", "score": c['score']}
|
|
2460
2479
|
for c in candidates[1:4]
|
|
@@ -2462,6 +2481,14 @@ class BasicMobileToolsLite:
|
|
|
2462
2481
|
"screen_size": {"width": screen_width, "height": screen_height}
|
|
2463
2482
|
}
|
|
2464
2483
|
|
|
2484
|
+
# 如果有 resource-id,额外提供
|
|
2485
|
+
if best.get('resource_id'):
|
|
2486
|
+
result["best_candidate"]["resource_id"] = best['resource_id']
|
|
2487
|
+
if best.get('text'):
|
|
2488
|
+
result["best_candidate"]["text"] = best['text']
|
|
2489
|
+
|
|
2490
|
+
return result
|
|
2491
|
+
|
|
2465
2492
|
except Exception as e:
|
|
2466
2493
|
return {"success": False, "message": f"❌ 查找关闭按钮失败: {e}"}
|
|
2467
2494
|
|
|
@@ -3442,6 +3469,16 @@ class BasicMobileToolsLite:
|
|
|
3442
3469
|
reason = f"文本含'{kw}'"
|
|
3443
3470
|
break
|
|
3444
3471
|
|
|
3472
|
+
# resource-id 匹配(如 close_icon, ad_close 等)
|
|
3473
|
+
if resource_id:
|
|
3474
|
+
res_id_lower = resource_id.lower()
|
|
3475
|
+
for kw in ['close', 'dismiss', 'skip', 'cancel']:
|
|
3476
|
+
if kw in res_id_lower:
|
|
3477
|
+
score += 9
|
|
3478
|
+
short_id = resource_id.split('/')[-1] if '/' in resource_id else resource_id
|
|
3479
|
+
reason = f"resource-id='{short_id}'"
|
|
3480
|
+
break
|
|
3481
|
+
|
|
3445
3482
|
# content-desc 匹配
|
|
3446
3483
|
for kw in close_content_desc:
|
|
3447
3484
|
if kw.lower() in content_desc.lower():
|
|
@@ -610,22 +610,25 @@ class MobileMCPServer:
|
|
|
610
610
|
name="mobile_find_close_button",
|
|
611
611
|
description="""🔍 智能查找关闭按钮(只找不点,返回位置)
|
|
612
612
|
|
|
613
|
-
|
|
613
|
+
⚡ 【推荐首选】遇到弹窗时优先调用此工具!无需先截图。
|
|
614
|
+
|
|
615
|
+
从元素树中找最可能的关闭按钮,返回坐标和推荐的点击命令。
|
|
614
616
|
|
|
615
617
|
🎯 识别策略(优先级):
|
|
616
|
-
1. 文本匹配:×、X、关闭、取消、跳过
|
|
617
|
-
2.
|
|
618
|
-
3.
|
|
618
|
+
1. 文本匹配:×、X、关闭、取消、跳过 等(得分100)
|
|
619
|
+
2. resource-id 匹配:包含 close/dismiss/skip(得分95)
|
|
620
|
+
3. content-desc 匹配:包含 close/关闭(得分90)
|
|
621
|
+
4. 小尺寸 clickable 元素(右上角优先,得分70+)
|
|
619
622
|
|
|
620
623
|
✅ 返回内容:
|
|
621
624
|
- 坐标 (x, y) 和百分比 (x%, y%)
|
|
622
|
-
-
|
|
623
|
-
-
|
|
625
|
+
- resource-id(如果有)
|
|
626
|
+
- 推荐的点击命令(优先 click_by_id,其次 click_by_text,最后 click_by_percent)
|
|
624
627
|
|
|
625
628
|
💡 使用流程:
|
|
626
|
-
1.
|
|
627
|
-
2.
|
|
628
|
-
3.
|
|
629
|
+
1. 直接调用此工具(无需先截图/列元素)
|
|
630
|
+
2. 根据返回的 click_command 执行点击
|
|
631
|
+
3. 如果返回 success=false,才需要截图分析""",
|
|
629
632
|
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
630
633
|
))
|
|
631
634
|
|
|
@@ -711,24 +714,26 @@ class MobileMCPServer:
|
|
|
711
714
|
name="mobile_close_ad",
|
|
712
715
|
description="""🚫 【推荐】智能关闭广告弹窗
|
|
713
716
|
|
|
714
|
-
|
|
717
|
+
⚡ 直接调用即可,无需先截图!会自动按优先级尝试:
|
|
715
718
|
|
|
716
|
-
1️⃣
|
|
717
|
-
- 自动查找
|
|
719
|
+
1️⃣ **控件树查找**(最可靠,优先)
|
|
720
|
+
- 自动查找 resource-id 包含 close/dismiss
|
|
721
|
+
- 查找文本"关闭"、"跳过"、"×"等
|
|
718
722
|
- 找到直接点击,实时可靠
|
|
719
723
|
|
|
720
724
|
2️⃣ **模板匹配**(次优)
|
|
721
725
|
- 用 OpenCV 匹配已保存的 X 按钮模板
|
|
722
|
-
-
|
|
726
|
+
- 模板越多成功率越高
|
|
723
727
|
|
|
724
728
|
3️⃣ **返回截图供 AI 分析**(兜底)
|
|
725
|
-
-
|
|
729
|
+
- 前两步都失败才截图
|
|
726
730
|
- AI 分析后用 mobile_click_by_percent 点击
|
|
727
|
-
- 点击成功后用 mobile_template_add
|
|
731
|
+
- 点击成功后用 mobile_template_add 添加模板
|
|
728
732
|
|
|
729
|
-
💡
|
|
730
|
-
1. 遇到广告弹窗 →
|
|
733
|
+
💡 正确流程:
|
|
734
|
+
1. 遇到广告弹窗 → 直接调用此工具
|
|
731
735
|
2. 如果成功 → 完成
|
|
736
|
+
3. 只有失败时才需要截图分析
|
|
732
737
|
3. 如果失败 → 看截图找 X → 点击 → 添加模板""",
|
|
733
738
|
inputSchema={
|
|
734
739
|
"type": "object",
|
|
@@ -65,6 +65,16 @@ templates/close_buttons/auto_x_0112_152840.png
|
|
|
65
65
|
templates/close_buttons/auto_x_0112_153256.png
|
|
66
66
|
templates/close_buttons/auto_x_0112_154847.png
|
|
67
67
|
templates/close_buttons/gray_x_stock_ad.png
|
|
68
|
+
tests/test_mind_cloud_my_space.py
|
|
69
|
+
tests/test_mind_correct.py
|
|
70
|
+
tests/test_mind_improved.py
|
|
71
|
+
tests/test_mind_optimized.py
|
|
72
|
+
tests/test_open_mind.py
|
|
73
|
+
tests/test_priority_demo.py
|
|
74
|
+
tests/test_simple.py
|
|
75
|
+
tests/test_举报.py
|
|
76
|
+
tests/test_切换语言到English.py
|
|
77
|
+
tests/test_测试.py
|
|
68
78
|
utils/__init__.py
|
|
69
79
|
utils/logger.py
|
|
70
80
|
utils/xml_formatter.py
|
|
@@ -25,7 +25,7 @@ if requirements_file.exists():
|
|
|
25
25
|
|
|
26
26
|
setup(
|
|
27
27
|
name="mobile-mcp-ai",
|
|
28
|
-
version="2.6.
|
|
28
|
+
version="2.6.1", # find_close_button 增加 resource-id 匹配 + list_elements 文本过滤
|
|
29
29
|
author="douzi",
|
|
30
30
|
author_email="1492994674@qq.com",
|
|
31
31
|
description="移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)",
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
测试用例: Mind云文档我的空间
|
|
5
|
+
生成时间: 2025-12-17 11:00:00
|
|
6
|
+
"""
|
|
7
|
+
import time
|
|
8
|
+
import uiautomator2 as u2
|
|
9
|
+
|
|
10
|
+
PACKAGE_NAME = "com.im30.mind"
|
|
11
|
+
|
|
12
|
+
# 广告关闭按钮关键词(可自定义)
|
|
13
|
+
AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def smart_wait(d, timeout=10):
|
|
17
|
+
"""智能等待页面稳定"""
|
|
18
|
+
d.implicitly_wait(timeout)
|
|
19
|
+
time.sleep(0.5) # 额外等待动画
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def close_ad_if_exists(d):
|
|
23
|
+
"""尝试关闭广告弹窗"""
|
|
24
|
+
for keyword in AD_CLOSE_KEYWORDS:
|
|
25
|
+
elem = d(textContains=keyword)
|
|
26
|
+
if elem.exists(timeout=0.5):
|
|
27
|
+
try:
|
|
28
|
+
elem.click()
|
|
29
|
+
print(f' 📢 关闭广告: {keyword}')
|
|
30
|
+
time.sleep(0.5)
|
|
31
|
+
return True
|
|
32
|
+
except:
|
|
33
|
+
pass
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def safe_click(d, selector, timeout=5):
|
|
38
|
+
"""安全点击(带等待和重试)"""
|
|
39
|
+
try:
|
|
40
|
+
if selector.exists(timeout=timeout):
|
|
41
|
+
selector.click()
|
|
42
|
+
return True
|
|
43
|
+
return False
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f' ⚠️ 点击失败: {e}')
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_main():
|
|
50
|
+
# 连接设备
|
|
51
|
+
d = u2.connect()
|
|
52
|
+
d.implicitly_wait(10) # 设置全局等待
|
|
53
|
+
|
|
54
|
+
# 启动应用
|
|
55
|
+
d.app_start(PACKAGE_NAME)
|
|
56
|
+
smart_wait(d)
|
|
57
|
+
|
|
58
|
+
# 尝试关闭启动广告
|
|
59
|
+
close_ad_if_exists(d)
|
|
60
|
+
|
|
61
|
+
# 步骤1: 点击文本 'Mind'
|
|
62
|
+
safe_click(d, d(text='Mind'))
|
|
63
|
+
smart_wait(d)
|
|
64
|
+
close_ad_if_exists(d) # 检查广告
|
|
65
|
+
|
|
66
|
+
# 步骤2: 点击坐标 (756, 2277)
|
|
67
|
+
d.click(756, 2277)
|
|
68
|
+
smart_wait(d)
|
|
69
|
+
close_ad_if_exists(d) # 检查广告
|
|
70
|
+
|
|
71
|
+
# 步骤3: 点击坐标 (815, 285)
|
|
72
|
+
d.click(815, 285)
|
|
73
|
+
smart_wait(d)
|
|
74
|
+
close_ad_if_exists(d) # 检查广告
|
|
75
|
+
|
|
76
|
+
print('✅ 测试完成')
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if __name__ == '__main__':
|
|
80
|
+
test_main()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
测试用例: Mind云文档我的空间_正确版
|
|
5
|
+
生成时间: 2025-12-17 11:08:10
|
|
6
|
+
"""
|
|
7
|
+
import time
|
|
8
|
+
import uiautomator2 as u2
|
|
9
|
+
|
|
10
|
+
PACKAGE_NAME = "com.im30.mind"
|
|
11
|
+
|
|
12
|
+
# 广告关闭按钮关键词(可自定义)
|
|
13
|
+
AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def smart_wait(d, seconds=1):
|
|
17
|
+
"""等待页面稳定"""
|
|
18
|
+
time.sleep(seconds)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def close_ad_if_exists(d, quick=False):
|
|
22
|
+
"""尝试关闭广告弹窗(quick=True 时只检查常见的)"""
|
|
23
|
+
keywords = AD_CLOSE_KEYWORDS[:3] if quick else AD_CLOSE_KEYWORDS
|
|
24
|
+
for keyword in keywords:
|
|
25
|
+
elem = d(textContains=keyword)
|
|
26
|
+
if elem.exists(timeout=0.3): # 缩短超时
|
|
27
|
+
try:
|
|
28
|
+
elem.click()
|
|
29
|
+
print(f' 📢 关闭广告: {keyword}')
|
|
30
|
+
time.sleep(0.3)
|
|
31
|
+
return True
|
|
32
|
+
except:
|
|
33
|
+
pass
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def safe_click(d, selector, timeout=3):
|
|
38
|
+
"""安全点击(带等待)"""
|
|
39
|
+
try:
|
|
40
|
+
if selector.exists(timeout=timeout):
|
|
41
|
+
selector.click()
|
|
42
|
+
return True
|
|
43
|
+
return False
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f' ⚠️ 点击失败: {e}')
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_main():
|
|
50
|
+
# 连接设备
|
|
51
|
+
d = u2.connect()
|
|
52
|
+
d.implicitly_wait(10) # 设置全局等待
|
|
53
|
+
|
|
54
|
+
# 启动应用
|
|
55
|
+
d.app_start(PACKAGE_NAME)
|
|
56
|
+
smart_wait(d)
|
|
57
|
+
|
|
58
|
+
# 尝试关闭启动广告
|
|
59
|
+
close_ad_if_exists(d)
|
|
60
|
+
|
|
61
|
+
# 步骤1: 点击坐标 (756, 2277)
|
|
62
|
+
d.click(756, 2277)
|
|
63
|
+
time.sleep(0.5) # 等待响应
|
|
64
|
+
|
|
65
|
+
# 步骤2: 点击坐标 (815, 285)
|
|
66
|
+
d.click(815, 285)
|
|
67
|
+
time.sleep(0.5) # 等待响应
|
|
68
|
+
|
|
69
|
+
print('✅ 测试完成')
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == '__main__':
|
|
73
|
+
test_main()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
测试用例: 打开Mind应用测试
|
|
5
|
+
生成时间: 2025-12-17 10:52:37
|
|
6
|
+
"""
|
|
7
|
+
import time
|
|
8
|
+
import uiautomator2 as u2
|
|
9
|
+
|
|
10
|
+
PACKAGE_NAME = "com.im30.mind"
|
|
11
|
+
|
|
12
|
+
# 广告关闭按钮关键词(可自定义)
|
|
13
|
+
AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def smart_wait(d, timeout=10):
|
|
17
|
+
"""智能等待页面稳定"""
|
|
18
|
+
d.implicitly_wait(timeout)
|
|
19
|
+
time.sleep(0.5) # 额外等待动画
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def close_ad_if_exists(d):
|
|
23
|
+
"""尝试关闭广告弹窗"""
|
|
24
|
+
for keyword in AD_CLOSE_KEYWORDS:
|
|
25
|
+
elem = d(textContains=keyword)
|
|
26
|
+
if elem.exists(timeout=0.5):
|
|
27
|
+
try:
|
|
28
|
+
elem.click()
|
|
29
|
+
print(f' 📢 关闭广告: {keyword}')
|
|
30
|
+
time.sleep(0.5)
|
|
31
|
+
return True
|
|
32
|
+
except:
|
|
33
|
+
pass
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def safe_click(d, selector, timeout=5):
|
|
38
|
+
"""安全点击(带等待和重试)"""
|
|
39
|
+
try:
|
|
40
|
+
if selector.exists(timeout=timeout):
|
|
41
|
+
selector.click()
|
|
42
|
+
return True
|
|
43
|
+
return False
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f' ⚠️ 点击失败: {e}')
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_main():
|
|
50
|
+
# 连接设备
|
|
51
|
+
d = u2.connect()
|
|
52
|
+
d.implicitly_wait(10) # 设置全局等待
|
|
53
|
+
|
|
54
|
+
# 启动应用
|
|
55
|
+
d.app_start(PACKAGE_NAME)
|
|
56
|
+
smart_wait(d)
|
|
57
|
+
|
|
58
|
+
# 尝试关闭启动广告
|
|
59
|
+
close_ad_if_exists(d)
|
|
60
|
+
|
|
61
|
+
# 步骤1: 点击文本 'Mind'
|
|
62
|
+
safe_click(d, d(text='Mind'))
|
|
63
|
+
smart_wait(d)
|
|
64
|
+
close_ad_if_exists(d) # 检查广告
|
|
65
|
+
|
|
66
|
+
# 步骤2: 点击坐标 (540, 1200)
|
|
67
|
+
d.click(540, 1200)
|
|
68
|
+
smart_wait(d)
|
|
69
|
+
close_ad_if_exists(d) # 检查广告
|
|
70
|
+
|
|
71
|
+
# 步骤3: 输入文本 '测试'
|
|
72
|
+
d(resourceId='com.im30.mind:id/search').set_text('测试')
|
|
73
|
+
smart_wait(d)
|
|
74
|
+
|
|
75
|
+
# 步骤4: 滑动 up
|
|
76
|
+
d.swipe_ext('up')
|
|
77
|
+
smart_wait(d)
|
|
78
|
+
|
|
79
|
+
print('✅ 测试完成')
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if __name__ == '__main__':
|
|
83
|
+
test_main()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
测试用例: Mind云文档我的空间_优化版
|
|
5
|
+
生成时间: 2025-12-17 11:04:53
|
|
6
|
+
"""
|
|
7
|
+
import time
|
|
8
|
+
import uiautomator2 as u2
|
|
9
|
+
|
|
10
|
+
PACKAGE_NAME = "com.im30.mind"
|
|
11
|
+
|
|
12
|
+
# 广告关闭按钮关键词(可自定义)
|
|
13
|
+
AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def smart_wait(d, seconds=1):
|
|
17
|
+
"""等待页面稳定"""
|
|
18
|
+
time.sleep(seconds)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def close_ad_if_exists(d, quick=False):
|
|
22
|
+
"""尝试关闭广告弹窗(quick=True 时只检查常见的)"""
|
|
23
|
+
keywords = AD_CLOSE_KEYWORDS[:3] if quick else AD_CLOSE_KEYWORDS
|
|
24
|
+
for keyword in keywords:
|
|
25
|
+
elem = d(textContains=keyword)
|
|
26
|
+
if elem.exists(timeout=0.3): # 缩短超时
|
|
27
|
+
try:
|
|
28
|
+
elem.click()
|
|
29
|
+
print(f' 📢 关闭广告: {keyword}')
|
|
30
|
+
time.sleep(0.3)
|
|
31
|
+
return True
|
|
32
|
+
except:
|
|
33
|
+
pass
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def safe_click(d, selector, timeout=3):
|
|
38
|
+
"""安全点击(带等待)"""
|
|
39
|
+
try:
|
|
40
|
+
if selector.exists(timeout=timeout):
|
|
41
|
+
selector.click()
|
|
42
|
+
return True
|
|
43
|
+
return False
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f' ⚠️ 点击失败: {e}')
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_main():
|
|
50
|
+
# 连接设备
|
|
51
|
+
d = u2.connect()
|
|
52
|
+
d.implicitly_wait(10) # 设置全局等待
|
|
53
|
+
|
|
54
|
+
# 启动应用
|
|
55
|
+
d.app_start(PACKAGE_NAME)
|
|
56
|
+
smart_wait(d)
|
|
57
|
+
|
|
58
|
+
# 尝试关闭启动广告
|
|
59
|
+
close_ad_if_exists(d)
|
|
60
|
+
|
|
61
|
+
# 步骤1: 点击文本 'Mind'
|
|
62
|
+
safe_click(d, d(text='Mind'))
|
|
63
|
+
time.sleep(0.5) # 等待响应
|
|
64
|
+
|
|
65
|
+
# 步骤2: 点击坐标 (756, 2277)
|
|
66
|
+
d.click(756, 2277)
|
|
67
|
+
time.sleep(0.5) # 等待响应
|
|
68
|
+
|
|
69
|
+
# 步骤3: 点击坐标 (815, 285)
|
|
70
|
+
d.click(815, 285)
|
|
71
|
+
time.sleep(0.5) # 等待响应
|
|
72
|
+
|
|
73
|
+
print('✅ 测试完成')
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == '__main__':
|
|
77
|
+
test_main()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
测试用例: 打开Mind应用测试
|
|
5
|
+
生成时间: 2025-12-17 10:50:37
|
|
6
|
+
"""
|
|
7
|
+
import time
|
|
8
|
+
import uiautomator2 as u2
|
|
9
|
+
|
|
10
|
+
PACKAGE_NAME = "com.im30.mind"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_main():
|
|
14
|
+
# 连接设备
|
|
15
|
+
d = u2.connect()
|
|
16
|
+
|
|
17
|
+
# 启动应用
|
|
18
|
+
d.app_start(PACKAGE_NAME)
|
|
19
|
+
time.sleep(3)
|
|
20
|
+
|
|
21
|
+
# 步骤1: 点击文本 Mind
|
|
22
|
+
d(text='Mind').click()
|
|
23
|
+
time.sleep(1)
|
|
24
|
+
|
|
25
|
+
# 步骤2: 点击坐标
|
|
26
|
+
d.click(540, 1200)
|
|
27
|
+
time.sleep(1)
|
|
28
|
+
|
|
29
|
+
# 步骤3: 输入文本
|
|
30
|
+
d(resourceId='com.im30.mind:id/search').set_text('测试')
|
|
31
|
+
time.sleep(1)
|
|
32
|
+
|
|
33
|
+
print('✅ 测试完成')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == '__main__':
|
|
37
|
+
test_main()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
测试用例: 优先文本ID_坐标兜底
|
|
5
|
+
生成时间: 2025-12-17 11:11:12
|
|
6
|
+
"""
|
|
7
|
+
import time
|
|
8
|
+
import uiautomator2 as u2
|
|
9
|
+
|
|
10
|
+
PACKAGE_NAME = "com.im30.mind"
|
|
11
|
+
|
|
12
|
+
# 广告关闭按钮关键词(可自定义)
|
|
13
|
+
AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def smart_wait(d, seconds=1):
|
|
17
|
+
"""等待页面稳定"""
|
|
18
|
+
time.sleep(seconds)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def close_ad_if_exists(d, quick=False):
|
|
22
|
+
"""尝试关闭广告弹窗(quick=True 时只检查常见的)"""
|
|
23
|
+
keywords = AD_CLOSE_KEYWORDS[:3] if quick else AD_CLOSE_KEYWORDS
|
|
24
|
+
for keyword in keywords:
|
|
25
|
+
elem = d(textContains=keyword)
|
|
26
|
+
if elem.exists(timeout=0.3): # 缩短超时
|
|
27
|
+
try:
|
|
28
|
+
elem.click()
|
|
29
|
+
print(f' 📢 关闭广告: {keyword}')
|
|
30
|
+
time.sleep(0.3)
|
|
31
|
+
return True
|
|
32
|
+
except:
|
|
33
|
+
pass
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def safe_click(d, selector, timeout=3):
|
|
38
|
+
"""安全点击(带等待)"""
|
|
39
|
+
try:
|
|
40
|
+
if selector.exists(timeout=timeout):
|
|
41
|
+
selector.click()
|
|
42
|
+
return True
|
|
43
|
+
return False
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print(f' ⚠️ 点击失败: {e}')
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_main():
|
|
50
|
+
# 连接设备
|
|
51
|
+
d = u2.connect()
|
|
52
|
+
d.implicitly_wait(10) # 设置全局等待
|
|
53
|
+
|
|
54
|
+
# 启动应用(等待 3 秒让启动页/广告加载)
|
|
55
|
+
d.app_start(PACKAGE_NAME)
|
|
56
|
+
time.sleep(3) # 等待启动页/广告
|
|
57
|
+
|
|
58
|
+
# 尝试关闭启动广告(最多尝试 3 次)
|
|
59
|
+
for _ in range(3):
|
|
60
|
+
if close_ad_if_exists(d):
|
|
61
|
+
time.sleep(1) # 关闭广告后等待
|
|
62
|
+
else:
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
# 步骤1: 点击文本 '云文档'
|
|
66
|
+
safe_click(d, d(text='云文档'))
|
|
67
|
+
time.sleep(0.5) # 等待响应
|
|
68
|
+
|
|
69
|
+
# 步骤2: 点击元素 我的空间
|
|
70
|
+
safe_click(d, d(resourceId='com.im30.mind:id/tab_my_space'))
|
|
71
|
+
time.sleep(0.5) # 等待响应
|
|
72
|
+
|
|
73
|
+
# 步骤3: 点击坐标 (某个按钮)
|
|
74
|
+
d.click(500, 800)
|
|
75
|
+
time.sleep(0.5) # 等待响应
|
|
76
|
+
|
|
77
|
+
print('✅ 测试完成')
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if __name__ == '__main__':
|
|
81
|
+
test_main()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
测试用例: 简化版脚本
|
|
5
|
+
生成时间: 2025-12-17 11:12:48
|
|
6
|
+
"""
|
|
7
|
+
import time
|
|
8
|
+
import uiautomator2 as u2
|
|
9
|
+
|
|
10
|
+
PACKAGE_NAME = "com.im30.mind"
|
|
11
|
+
|
|
12
|
+
# === 配置(根据 App 情况调整)===
|
|
13
|
+
LAUNCH_WAIT = 3 # 启动后等待时间(秒)
|
|
14
|
+
CLOSE_AD_ON_LAUNCH = True # 是否尝试关闭启动广告
|
|
15
|
+
AD_CLOSE_KEYWORDS = ['关闭', '跳过', 'Skip', 'Close', '×', 'X', '我知道了', '稍后再说']
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def smart_wait(d, seconds=1):
|
|
19
|
+
"""等待页面稳定"""
|
|
20
|
+
time.sleep(seconds)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def close_ad_if_exists(d, quick=False):
|
|
24
|
+
"""尝试关闭广告弹窗(quick=True 时只检查常见的)"""
|
|
25
|
+
keywords = AD_CLOSE_KEYWORDS[:3] if quick else AD_CLOSE_KEYWORDS
|
|
26
|
+
for keyword in keywords:
|
|
27
|
+
elem = d(textContains=keyword)
|
|
28
|
+
if elem.exists(timeout=0.3): # 缩短超时
|
|
29
|
+
try:
|
|
30
|
+
elem.click()
|
|
31
|
+
print(f' 📢 关闭广告: {keyword}')
|
|
32
|
+
time.sleep(0.3)
|
|
33
|
+
return True
|
|
34
|
+
except:
|
|
35
|
+
pass
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def safe_click(d, selector, timeout=3):
|
|
40
|
+
"""安全点击(带等待)"""
|
|
41
|
+
try:
|
|
42
|
+
if selector.exists(timeout=timeout):
|
|
43
|
+
selector.click()
|
|
44
|
+
return True
|
|
45
|
+
return False
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f' ⚠️ 点击失败: {e}')
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_main():
|
|
52
|
+
# 连接设备
|
|
53
|
+
d = u2.connect()
|
|
54
|
+
d.implicitly_wait(10) # 设置全局等待
|
|
55
|
+
|
|
56
|
+
# 启动应用
|
|
57
|
+
d.app_start(PACKAGE_NAME)
|
|
58
|
+
time.sleep(LAUNCH_WAIT) # 等待启动(可调整)
|
|
59
|
+
|
|
60
|
+
# 尝试关闭启动广告(可选,根据 App 情况调整)
|
|
61
|
+
if CLOSE_AD_ON_LAUNCH:
|
|
62
|
+
close_ad_if_exists(d)
|
|
63
|
+
|
|
64
|
+
# 步骤1: 点击文本 '云文档'
|
|
65
|
+
safe_click(d, d(text='云文档'))
|
|
66
|
+
time.sleep(0.5) # 等待响应
|
|
67
|
+
|
|
68
|
+
# 步骤2: 点击文本 '我的空间'
|
|
69
|
+
safe_click(d, d(text='我的空间'))
|
|
70
|
+
time.sleep(0.5) # 等待响应
|
|
71
|
+
|
|
72
|
+
print('✅ 测试完成')
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == '__main__':
|
|
76
|
+
test_main()
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
移动端测试用例: 举报测试
|
|
5
|
+
生成时间: 2025-11-25 15:45:38
|
|
6
|
+
|
|
7
|
+
⚠️ 注意:此脚本基于AI执行历史生成,使用已验证的定位方式
|
|
8
|
+
如果页面结构变化,可能需要重新生成脚本
|
|
9
|
+
📊 执行统计:
|
|
10
|
+
- 总操作数: 9
|
|
11
|
+
- 成功操作: 8
|
|
12
|
+
- 失败尝试: 1
|
|
13
|
+
- 成功率: 88.9%
|
|
14
|
+
|
|
15
|
+
💡 说明:此脚本经过多次尝试后生成,只包含最终成功的操作步骤
|
|
16
|
+
|
|
17
|
+
运行方式:
|
|
18
|
+
pytest 举报测试.py -v
|
|
19
|
+
pytest 举报测试.py --alluredir=./allure-results # 生成allure报告
|
|
20
|
+
"""
|
|
21
|
+
import asyncio
|
|
22
|
+
import pytest
|
|
23
|
+
import pytest_asyncio
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
# 添加backend目录到路径
|
|
28
|
+
# tests目录结构: backend/mobile_mcp/tests/test_xxx.py
|
|
29
|
+
# 需要导入: backend/mobile_mcp/core/mobile_client.py
|
|
30
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
31
|
+
|
|
32
|
+
from mobile_mcp.core.mobile_client import MobileClient
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
PACKAGE_NAME = "com.im30.way"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest_asyncio.fixture(scope='function')
|
|
39
|
+
async def mobile_client():
|
|
40
|
+
"""
|
|
41
|
+
pytest fixture: 创建并返回MobileClient实例
|
|
42
|
+
scope='function': 每个测试函数都会创建一个新的client
|
|
43
|
+
"""
|
|
44
|
+
client = MobileClient(device_id=None)
|
|
45
|
+
|
|
46
|
+
# 启动App
|
|
47
|
+
print(f"\n📱 启动App: {{PACKAGE_NAME}}")
|
|
48
|
+
result = await client.launch_app(PACKAGE_NAME, wait_time=5)
|
|
49
|
+
if not result.get('success'):
|
|
50
|
+
raise Exception(f"启动App失败: {{result.get('reason')}}")
|
|
51
|
+
|
|
52
|
+
await asyncio.sleep(2) # 等待页面加载
|
|
53
|
+
|
|
54
|
+
yield client
|
|
55
|
+
|
|
56
|
+
# 清理
|
|
57
|
+
client.device_manager.disconnect()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.mark.asyncio
|
|
61
|
+
async def test_举报测试(mobile_client):
|
|
62
|
+
"""
|
|
63
|
+
测试用例: 举报测试
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
mobile_client: pytest fixture,已启动App的MobileClient实例
|
|
67
|
+
"""
|
|
68
|
+
client = mobile_client
|
|
69
|
+
|
|
70
|
+
print("=" * 60)
|
|
71
|
+
print(f"🚀 举报测试")
|
|
72
|
+
print("=" * 60)
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
# 步骤1: 点击 [810,2186][1080,2356]
|
|
76
|
+
print(f"\n步骤1: 点击 [810,2186][1080,2356]")
|
|
77
|
+
# ✅ 使用bounds坐标(已验证)
|
|
78
|
+
await client.click("[810,2186][1080,2356]", ref="[810,2186][1080,2356]", verify=False)
|
|
79
|
+
print(f"✅ 点击成功(bounds: [810,2186][1080,2356])")
|
|
80
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
81
|
+
# 步骤2: 点击 [919,113][1034,205]
|
|
82
|
+
print(f"\n步骤2: 点击 [919,113][1034,205]")
|
|
83
|
+
# ✅ 使用bounds坐标(已验证)
|
|
84
|
+
await client.click("[919,113][1034,205]", ref="[919,113][1034,205]", verify=False)
|
|
85
|
+
print(f"✅ 点击成功(bounds: [919,113][1034,205])")
|
|
86
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
87
|
+
# 步骤3: 点击 [861,131][919,188]
|
|
88
|
+
print(f"\n步骤3: 点击 [861,131][919,188]")
|
|
89
|
+
# ✅ 使用bounds坐标(已验证)
|
|
90
|
+
await client.click("[861,131][919,188]", ref="[861,131][919,188]", verify=False)
|
|
91
|
+
print(f"✅ 点击成功(bounds: [861,131][919,188])")
|
|
92
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
93
|
+
# 步骤4: 点击 举报
|
|
94
|
+
print(f"\n步骤4: 点击 举报")
|
|
95
|
+
# ✅ 使用bounds坐标(已验证)
|
|
96
|
+
await client.click("举报", ref="[515,1557][565,1607]", verify=False)
|
|
97
|
+
print(f"✅ 点击成功(bounds: [515,1557][565,1607])")
|
|
98
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
99
|
+
# 步骤5: 点击 [0,1333][1080,1460]
|
|
100
|
+
print(f"\n步骤5: 点击 [0,1333][1080,1460]")
|
|
101
|
+
# ✅ 使用bounds坐标(已验证)
|
|
102
|
+
await client.click("[0,1333][1080,1460]", ref="[0,1333][1080,1460]", verify=False)
|
|
103
|
+
print(f"✅ 点击成功(bounds: [0,1333][1080,1460])")
|
|
104
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
105
|
+
# 步骤6: 点击 [81,292][999,826]
|
|
106
|
+
print(f"\n步骤6: 点击 [81,292][999,826]")
|
|
107
|
+
# ✅ 使用bounds坐标(已验证)
|
|
108
|
+
await client.click("[81,292][999,826]", ref="[81,292][999,826]", verify=False)
|
|
109
|
+
print(f"✅ 点击成功(bounds: [81,292][999,826])")
|
|
110
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
111
|
+
# 步骤7: 在[81,292][999,826]输入 举报自动化测试
|
|
112
|
+
print(f"\n步骤7: 在[81,292][999,826]输入 举报自动化测试")
|
|
113
|
+
# ✅ 使用bounds坐标输入(已验证)
|
|
114
|
+
await client.type_text("[81,292][999,826]", "举报自动化测试", ref="[81,292][999,826]")
|
|
115
|
+
print(f"✅ 输入成功(bounds: [81,292][999,826])")
|
|
116
|
+
await asyncio.sleep(1) # 等待输入完成
|
|
117
|
+
# 步骤8: 点击 提交
|
|
118
|
+
print(f"\n步骤8: 点击 提交")
|
|
119
|
+
# ✅ 使用bounds坐标(已验证)
|
|
120
|
+
await client.click("提交", ref="[515,1003][565,1053]", verify=False)
|
|
121
|
+
print(f"✅ 点击成功(bounds: [515,1003][565,1053])")
|
|
122
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
123
|
+
|
|
124
|
+
print("\n✅ 测试完成!")
|
|
125
|
+
|
|
126
|
+
except AssertionError as e:
|
|
127
|
+
print(f"\n❌ 断言失败: {e}")
|
|
128
|
+
# 打印当前页面快照以便调试
|
|
129
|
+
snapshot = await client.snapshot()
|
|
130
|
+
print(f"\n当前页面快照:\n{snapshot[:500]}...")
|
|
131
|
+
raise
|
|
132
|
+
except Exception as e:
|
|
133
|
+
print(f"\n❌ 测试失败: {e}")
|
|
134
|
+
import traceback
|
|
135
|
+
traceback.print_exc()
|
|
136
|
+
raise
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
移动端测试用例: 切换语言到English
|
|
5
|
+
生成时间: 2025-11-24 16:42:21
|
|
6
|
+
|
|
7
|
+
⚠️ 注意:此脚本基于AI执行历史生成,使用已验证的定位方式
|
|
8
|
+
如果页面结构变化,可能需要重新生成脚本
|
|
9
|
+
|
|
10
|
+
运行方式:
|
|
11
|
+
pytest 切换语言到English.py -v
|
|
12
|
+
pytest 切换语言到English.py --alluredir=./allure-results # 生成allure报告
|
|
13
|
+
"""
|
|
14
|
+
import asyncio
|
|
15
|
+
import pytest
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
# 添加backend目录到路径
|
|
20
|
+
# tests目录结构: backend/mobile_mcp/tests/test_xxx.py
|
|
21
|
+
# 需要导入: backend/mobile_mcp/core/mobile_client.py
|
|
22
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
23
|
+
|
|
24
|
+
from mobile_mcp.core.mobile_client import MobileClient
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
PACKAGE_NAME = "com.im30.way"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture(scope='function')
|
|
31
|
+
async def mobile_client():
|
|
32
|
+
"""
|
|
33
|
+
pytest fixture: 创建并返回MobileClient实例
|
|
34
|
+
scope='function': 每个测试函数都会创建一个新的client
|
|
35
|
+
"""
|
|
36
|
+
client = MobileClient(device_id=None)
|
|
37
|
+
|
|
38
|
+
# 启动App
|
|
39
|
+
print(f"\n📱 启动App: {{PACKAGE_NAME}}")
|
|
40
|
+
result = await client.launch_app(PACKAGE_NAME, wait_time=5)
|
|
41
|
+
if not result.get('success'):
|
|
42
|
+
raise Exception(f"启动App失败: {{result.get('reason')}}")
|
|
43
|
+
|
|
44
|
+
await asyncio.sleep(2) # 等待页面加载
|
|
45
|
+
|
|
46
|
+
yield client
|
|
47
|
+
|
|
48
|
+
# 清理
|
|
49
|
+
client.device_manager.disconnect()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.mark.asyncio
|
|
53
|
+
async def test_切换语言到english(mobile_client):
|
|
54
|
+
"""
|
|
55
|
+
测试用例: 切换语言到English
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
mobile_client: pytest fixture,已启动App的MobileClient实例
|
|
59
|
+
"""
|
|
60
|
+
client = mobile_client
|
|
61
|
+
|
|
62
|
+
print("=" * 60)
|
|
63
|
+
print(f"🚀 切换语言到English")
|
|
64
|
+
print("=" * 60)
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
# 步骤1: 点击 [810,2186][1080,2356]
|
|
68
|
+
print(f"\n步骤1: 点击 [810,2186][1080,2356]")
|
|
69
|
+
# ✅ 使用bounds坐标(已验证)
|
|
70
|
+
await client.click("[810,2186][1080,2356]", ref="[810,2186][1080,2356]", verify=False)
|
|
71
|
+
print(f"✅ 点击成功(bounds: [810,2186][1080,2356])")
|
|
72
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
73
|
+
# 步骤2: 点击 右上角图标
|
|
74
|
+
print(f"\n步骤2: 点击 右上角图标")
|
|
75
|
+
# ✅ 使用bounds坐标(已验证)
|
|
76
|
+
await client.click("右上角图标", ref="[861,131][919,188]", verify=False)
|
|
77
|
+
print(f"✅ 点击成功(bounds: [861,131][919,188])")
|
|
78
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
79
|
+
# 步骤3: 点击 设置
|
|
80
|
+
print(f"\n步骤3: 点击 设置")
|
|
81
|
+
# ✅ 使用text/description定位(已验证)
|
|
82
|
+
await client.click("设置", ref="设置", verify=False)
|
|
83
|
+
print(f"✅ 点击成功(text/desc: 设置)")
|
|
84
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
85
|
+
# 步骤4: 点击 语言
|
|
86
|
+
print(f"\n步骤4: 点击 语言")
|
|
87
|
+
# ✅ 使用bounds坐标(已验证)
|
|
88
|
+
await client.click("语言", ref="[515,1170][565,1220]", verify=False)
|
|
89
|
+
print(f"✅ 点击成功(bounds: [515,1170][565,1220])")
|
|
90
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
91
|
+
# 步骤5: 点击 语言
|
|
92
|
+
print(f"\n步骤5: 点击 语言")
|
|
93
|
+
# ✅ 使用bounds坐标(已验证)
|
|
94
|
+
await client.click("语言", ref="[515,1170][565,1220]", verify=False)
|
|
95
|
+
print(f"✅ 点击成功(bounds: [515,1170][565,1220])")
|
|
96
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
97
|
+
# 步骤6: 点击 [810,2186][1080,2356]
|
|
98
|
+
print(f"\n步骤6: 点击 [810,2186][1080,2356]")
|
|
99
|
+
# ✅ 使用bounds坐标(已验证)
|
|
100
|
+
await client.click("[810,2186][1080,2356]", ref="[810,2186][1080,2356]", verify=False)
|
|
101
|
+
print(f"✅ 点击成功(bounds: [810,2186][1080,2356])")
|
|
102
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
103
|
+
# 步骤7: 点击 右上角设置
|
|
104
|
+
print(f"\n步骤7: 点击 右上角设置")
|
|
105
|
+
# ✅ 使用bounds坐标(已验证)
|
|
106
|
+
await client.click("右上角设置", ref="[919,113][1034,205]", verify=False)
|
|
107
|
+
print(f"✅ 点击成功(bounds: [919,113][1034,205])")
|
|
108
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
109
|
+
# 步骤8: 点击 [861,131][919,188]
|
|
110
|
+
print(f"\n步骤8: 点击 [861,131][919,188]")
|
|
111
|
+
# ✅ 使用bounds坐标(已验证)
|
|
112
|
+
await client.click("[861,131][919,188]", ref="[861,131][919,188]", verify=False)
|
|
113
|
+
print(f"✅ 点击成功(bounds: [861,131][919,188])")
|
|
114
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
115
|
+
# 步骤9: 点击 语言
|
|
116
|
+
print(f"\n步骤9: 点击 语言")
|
|
117
|
+
# ✅ 使用bounds坐标(已验证)
|
|
118
|
+
await client.click("语言", ref="[515,1170][565,1220]", verify=False)
|
|
119
|
+
print(f"✅ 点击成功(bounds: [515,1170][565,1220])")
|
|
120
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
121
|
+
# 步骤10: 点击 [0,1075][1080,1202]
|
|
122
|
+
print(f"\n步骤10: 点击 [0,1075][1080,1202]")
|
|
123
|
+
# ✅ 使用bounds坐标(已验证)
|
|
124
|
+
await client.click("[0,1075][1080,1202]", ref="[0,1075][1080,1202]", verify=False)
|
|
125
|
+
print(f"✅ 点击成功(bounds: [0,1075][1080,1202])")
|
|
126
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
127
|
+
# 步骤11: 点击 English
|
|
128
|
+
print(f"\n步骤11: 点击 English")
|
|
129
|
+
# ✅ 使用bounds坐标(已验证)
|
|
130
|
+
await client.click("English", ref="[515,325][565,375]", verify=False)
|
|
131
|
+
print(f"✅ 点击成功(bounds: [515,325][565,375])")
|
|
132
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
133
|
+
# 步骤12: 点击 保存
|
|
134
|
+
print(f"\n步骤12: 点击 保存")
|
|
135
|
+
# ✅ 使用text/description定位(已验证)
|
|
136
|
+
await client.click("保存", ref="保存", verify=False)
|
|
137
|
+
print(f"✅ 点击成功(text/desc: 保存)")
|
|
138
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
139
|
+
# 步骤13: 点击 重新启动
|
|
140
|
+
print(f"\n步骤13: 点击 重新启动")
|
|
141
|
+
# ✅ 使用text/description定位(已验证)
|
|
142
|
+
await client.click("重新启动", ref="重新启动", verify=False)
|
|
143
|
+
print(f"✅ 点击成功(text/desc: 重新启动)")
|
|
144
|
+
await asyncio.sleep(1.5) # 等待页面响应
|
|
145
|
+
|
|
146
|
+
print("\n✅ 测试完成!")
|
|
147
|
+
|
|
148
|
+
except AssertionError as e:
|
|
149
|
+
print(f"\n❌ 断言失败: {e}")
|
|
150
|
+
# 打印当前页面快照以便调试
|
|
151
|
+
snapshot = await client.snapshot()
|
|
152
|
+
print(f"\n当前页面快照:\n{snapshot[:500]}...")
|
|
153
|
+
raise
|
|
154
|
+
except Exception as e:
|
|
155
|
+
print(f"\n❌ 测试失败: {e}")
|
|
156
|
+
import traceback
|
|
157
|
+
traceback.print_exc()
|
|
158
|
+
raise
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
移动端自动化测试: 堆糖搜索测试
|
|
5
|
+
生成时间: 2025-11-26 15:39:24
|
|
6
|
+
|
|
7
|
+
依赖: pip install uiautomator2 pytest pytest-asyncio
|
|
8
|
+
|
|
9
|
+
运行方式:
|
|
10
|
+
pytest test_测试.py -v -s
|
|
11
|
+
pytest test_测试.py --alluredir=./allure-results # 生成allure报告
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import time
|
|
15
|
+
import pytest
|
|
16
|
+
import uiautomator2 as u2
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
PACKAGE_NAME = "com.duitang.main"
|
|
20
|
+
DEVICE_ID = "BEWGF6LFZ5RGS875" # 本地iOS设备 # None表示自动选择第一个设备
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture(scope='function')
|
|
24
|
+
def device():
|
|
25
|
+
"""
|
|
26
|
+
pytest fixture: 创建并返回设备连接
|
|
27
|
+
scope='function': 每个测试函数都会创建一个新的连接
|
|
28
|
+
"""
|
|
29
|
+
# 连接设备
|
|
30
|
+
d = u2.connect(DEVICE_ID) # None表示自动选择第一个设备
|
|
31
|
+
print(f"\n📱 连接设备: {d.device_info}")
|
|
32
|
+
|
|
33
|
+
# 启动App
|
|
34
|
+
print(f"🚀 启动App: {PACKAGE_NAME}")
|
|
35
|
+
d.app_start(PACKAGE_NAME, stop=True)
|
|
36
|
+
time.sleep(3) # 等待App启动
|
|
37
|
+
|
|
38
|
+
yield d
|
|
39
|
+
|
|
40
|
+
# 清理(可选:关闭App)
|
|
41
|
+
# d.app_stop(PACKAGE_NAME)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_堆糖搜索测试(device):
|
|
45
|
+
"""
|
|
46
|
+
测试用例: 堆糖搜索测试
|
|
47
|
+
|
|
48
|
+
测试步骤:
|
|
49
|
+
1. 打开com.duitang.main
|
|
50
|
+
2. 点击底部"我"
|
|
51
|
+
3. 点击"不同意"
|
|
52
|
+
4. 点击"首页"
|
|
53
|
+
5. 搜索框输入"测试"
|
|
54
|
+
6. 点击"搜索"
|
|
55
|
+
7. 点击返回
|
|
56
|
+
8. 点击返回
|
|
57
|
+
9. 断言回到了首页
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
device: pytest fixture,已启动App的设备连接
|
|
61
|
+
"""
|
|
62
|
+
d = device
|
|
63
|
+
|
|
64
|
+
# 步骤1: 点击底部"我"
|
|
65
|
+
print(f"\n步骤1: 点击底部'我'")
|
|
66
|
+
d.click(972, 2288) # 使用MCP验证过的坐标
|
|
67
|
+
time.sleep(1.5)
|
|
68
|
+
|
|
69
|
+
# 步骤2: 点击"不同意"
|
|
70
|
+
print(f"\n步骤2: 点击'不同意'")
|
|
71
|
+
d(resourceId="com.duitang.main:id/welcome_policies_disagree").click()
|
|
72
|
+
time.sleep(1.5)
|
|
73
|
+
|
|
74
|
+
# 步骤3: 点击"首页"
|
|
75
|
+
print(f"\n步骤3: 点击'首页'")
|
|
76
|
+
d(resourceId="com.duitang.main:id/ex_tab_title", text="首页").click()
|
|
77
|
+
time.sleep(1.5)
|
|
78
|
+
|
|
79
|
+
# 步骤4: 点击搜索框
|
|
80
|
+
print(f"\n步骤4: 点击搜索框")
|
|
81
|
+
d.click(540, 338) # 使用MCP验证过的坐标
|
|
82
|
+
time.sleep(1.5)
|
|
83
|
+
|
|
84
|
+
# 步骤5: 点击搜索输入框
|
|
85
|
+
print(f"\n步骤5: 点击搜索输入框")
|
|
86
|
+
d(resourceId="com.duitang.main:id/etSearch").click()
|
|
87
|
+
time.sleep(1.5)
|
|
88
|
+
|
|
89
|
+
# 步骤6: 点击最近搜索"测试"
|
|
90
|
+
print(f"\n步骤6: 点击最近搜索'测试'")
|
|
91
|
+
d.click(118, 396) # 使用MCP验证过的坐标
|
|
92
|
+
time.sleep(1.5)
|
|
93
|
+
|
|
94
|
+
# 步骤7: 点击"搜索"
|
|
95
|
+
print(f"\n步骤7: 点击'搜索'")
|
|
96
|
+
d(resourceId="com.duitang.main:id/search_bar_search_btn").click()
|
|
97
|
+
time.sleep(1.5)
|
|
98
|
+
|
|
99
|
+
# 步骤8: 点击返回
|
|
100
|
+
print(f"\n步骤8: 点击返回")
|
|
101
|
+
d.press("back")
|
|
102
|
+
time.sleep(1.5)
|
|
103
|
+
|
|
104
|
+
# 步骤9: 点击返回
|
|
105
|
+
print(f"\n步骤9: 点击返回")
|
|
106
|
+
d.press("back")
|
|
107
|
+
time.sleep(1.5)
|
|
108
|
+
|
|
109
|
+
# 步骤10: 断言回到了首页
|
|
110
|
+
print(f"\n步骤10: 断言回到了首页")
|
|
111
|
+
assert d(text="首页").exists(), "断言失败: 未能回到首页"
|
|
112
|
+
|
|
113
|
+
# ✅ 测试完成
|
|
114
|
+
print("✅ 测试通过")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_151217.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_152037.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_152840.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_153256.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/auto_x_0112_154847.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.6.0 → mobile_mcp_ai-2.6.1}/core/templates/close_buttons/gray_x_stock_ad.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|