mobile-mcp-ai 2.7.11__tar.gz → 2.7.12__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.7.11/mobile_mcp_ai.egg-info → mobile_mcp_ai-2.7.12}/PKG-INFO +2 -1
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/README.md +1 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/basic_tools_lite.py +77 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/mcp_tools/mcp_server.py +10 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12/mobile_mcp_ai.egg-info}/PKG-INFO +2 -1
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/mobile_mcp_ai.egg-info/SOURCES.txt +10 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/setup.py +1 -1
- mobile_mcp_ai-2.7.12/tests/test_mind_cloud_my_space.py +80 -0
- mobile_mcp_ai-2.7.12/tests/test_mind_correct.py +73 -0
- mobile_mcp_ai-2.7.12/tests/test_mind_improved.py +83 -0
- mobile_mcp_ai-2.7.12/tests/test_mind_optimized.py +77 -0
- mobile_mcp_ai-2.7.12/tests/test_open_mind.py +37 -0
- mobile_mcp_ai-2.7.12/tests/test_priority_demo.py +81 -0
- mobile_mcp_ai-2.7.12/tests/test_simple.py +76 -0
- mobile_mcp_ai-2.7.12/tests/test_/344/270/276/346/212/245.py +136 -0
- mobile_mcp_ai-2.7.12/tests/test_/345/210/207/346/215/242/350/257/255/350/250/200/345/210/260English.py +158 -0
- mobile_mcp_ai-2.7.12/tests/test_/346/265/213/350/257/225.py +114 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/LICENSE +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/MANIFEST.in +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/__init__.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/config.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/__init__.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/device_manager.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/dynamic_config.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/ios_client_wda.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/ios_device_manager_wda.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/mobile_client.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/template_matcher.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/utils/__init__.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/utils/logger.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/utils/operation_history_manager.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/utils/smart_wait.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/docs/iOS_SETUP_GUIDE.md +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/mcp_tools/__init__.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/mobile_mcp_ai.egg-info/dependency_links.txt +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/mobile_mcp_ai.egg-info/entry_points.txt +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/mobile_mcp_ai.egg-info/not-zip-safe +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/mobile_mcp_ai.egg-info/requires.txt +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/mobile_mcp_ai.egg-info/top_level.txt +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/requirements.txt +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/setup.cfg +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/templates/close_buttons/auto_x_0112_151217.png +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/templates/close_buttons/auto_x_0112_152037.png +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/templates/close_buttons/auto_x_0112_152840.png +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/templates/close_buttons/auto_x_0112_153256.png +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/templates/close_buttons/auto_x_0112_154847.png +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/templates/close_buttons/gray_x_stock_ad.png +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/utils/__init__.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/utils/logger.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/utils/xml_formatter.py +0 -0
- {mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/utils/xml_parser.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mobile-mcp-ai
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.12
|
|
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
|
|
@@ -460,6 +460,7 @@ pip install pyautogui pyperclip pygetwindow
|
|
|
460
460
|
| 👆 | `mobile_swipe` | 滑动 |
|
|
461
461
|
| ⌨️ | `mobile_press_key` | 按键 |
|
|
462
462
|
| ⏱️ | `mobile_wait` | 等待 |
|
|
463
|
+
| ⌨️ | `mobile_hide_keyboard` | 收起键盘(登录场景必备) |
|
|
463
464
|
| 📦 | `mobile_launch_app` | 启动应用 |
|
|
464
465
|
| 📦 | `mobile_terminate_app` | 终止应用 |
|
|
465
466
|
| 📦 | `mobile_list_apps` | 列出应用 |
|
|
@@ -382,6 +382,7 @@ pip install pyautogui pyperclip pygetwindow
|
|
|
382
382
|
| 👆 | `mobile_swipe` | 滑动 |
|
|
383
383
|
| ⌨️ | `mobile_press_key` | 按键 |
|
|
384
384
|
| ⏱️ | `mobile_wait` | 等待 |
|
|
385
|
+
| ⌨️ | `mobile_hide_keyboard` | 收起键盘(登录场景必备) |
|
|
385
386
|
| 📦 | `mobile_launch_app` | 启动应用 |
|
|
386
387
|
| 📦 | `mobile_terminate_app` | 终止应用 |
|
|
387
388
|
| 📦 | `mobile_list_apps` | 列出应用 |
|
|
@@ -2310,6 +2310,83 @@ class BasicMobileToolsLite:
|
|
|
2310
2310
|
except Exception as e:
|
|
2311
2311
|
return {"success": False, "message": f"❌ 按键失败: {e}"}
|
|
2312
2312
|
|
|
2313
|
+
async def hide_keyboard(self) -> Dict:
|
|
2314
|
+
"""收起键盘
|
|
2315
|
+
|
|
2316
|
+
在输入完成后收起键盘,确保页面元素不被键盘遮挡。
|
|
2317
|
+
对于登录场景中需要勾选协议复选框非常有用。
|
|
2318
|
+
|
|
2319
|
+
Returns:
|
|
2320
|
+
包含操作结果的字典
|
|
2321
|
+
"""
|
|
2322
|
+
try:
|
|
2323
|
+
if self._is_ios():
|
|
2324
|
+
ios_client = self._get_ios_client()
|
|
2325
|
+
if ios_client and hasattr(ios_client, 'wda'):
|
|
2326
|
+
# iOS: 尝试点击键盘上的"完成"/"Done"按钮或发送回车收起键盘
|
|
2327
|
+
try:
|
|
2328
|
+
# 尝试点击键盘上的"完成"或"Done"按钮
|
|
2329
|
+
keyboard = ios_client.wda(className='XCUIElementTypeKeyboard')
|
|
2330
|
+
if keyboard.exists:
|
|
2331
|
+
# 尝试找 return/done/完成 按钮
|
|
2332
|
+
for btn_name in ['return', 'Return', 'done', 'Done', '完成']:
|
|
2333
|
+
try:
|
|
2334
|
+
btn = keyboard.buttons[btn_name]
|
|
2335
|
+
if btn.exists:
|
|
2336
|
+
btn.click()
|
|
2337
|
+
return {"success": True, "message": "✅ 键盘已收起 (iOS - 点击完成按钮)"}
|
|
2338
|
+
except:
|
|
2339
|
+
continue
|
|
2340
|
+
|
|
2341
|
+
# 如果没有找到按钮,尝试点击键盘以外的区域
|
|
2342
|
+
size = ios_client.wda.window_size()
|
|
2343
|
+
# 点击屏幕顶部区域(通常不会被键盘遮挡)
|
|
2344
|
+
ios_client.wda.click(size[0] // 2, 50)
|
|
2345
|
+
return {"success": True, "message": "✅ 键盘已收起 (iOS - 点击空白区域)"}
|
|
2346
|
+
else:
|
|
2347
|
+
return {"success": True, "message": "💡 键盘未显示,无需收起"}
|
|
2348
|
+
except Exception as e:
|
|
2349
|
+
# 退回到发送回车键
|
|
2350
|
+
ios_client.wda.send_keys('\n')
|
|
2351
|
+
return {"success": True, "message": f"✅ 键盘已收起 (iOS - 发送回车,原因: {e})"}
|
|
2352
|
+
return {"success": False, "message": "❌ iOS 客户端未初始化"}
|
|
2353
|
+
else:
|
|
2354
|
+
# Android: 使用 back 键或 adb 命令收起键盘
|
|
2355
|
+
try:
|
|
2356
|
+
# 方法1: 使用 adb shell 检测键盘状态
|
|
2357
|
+
# u2.shell() 返回 ShellResponse(output, exit_code)
|
|
2358
|
+
shell_result = self.client.u2.shell('dumpsys input_method | grep mInputShown')
|
|
2359
|
+
# 提取 output 字段(兼容字符串和 ShellResponse 两种情况)
|
|
2360
|
+
result = shell_result.output if hasattr(shell_result, 'output') else str(shell_result)
|
|
2361
|
+
|
|
2362
|
+
if 'mInputShown=true' in result:
|
|
2363
|
+
# 键盘正在显示,按返回键收起
|
|
2364
|
+
self.client.u2.shell('input keyevent 4') # KEYCODE_BACK
|
|
2365
|
+
time.sleep(0.3)
|
|
2366
|
+
|
|
2367
|
+
# 验证键盘是否已收起
|
|
2368
|
+
shell_result_after = self.client.u2.shell('dumpsys input_method | grep mInputShown')
|
|
2369
|
+
result_after = shell_result_after.output if hasattr(shell_result_after, 'output') else str(shell_result_after)
|
|
2370
|
+
|
|
2371
|
+
if 'mInputShown=false' in result_after:
|
|
2372
|
+
return {"success": True, "message": "✅ 键盘已收起 (Android - back键)"}
|
|
2373
|
+
else:
|
|
2374
|
+
# 如果 back 键没效果,尝试点击空白区域
|
|
2375
|
+
info = self.client.u2.info
|
|
2376
|
+
height = info.get('displayHeight', 1920)
|
|
2377
|
+
width = info.get('displayWidth', 1080)
|
|
2378
|
+
# 点击标题栏区域
|
|
2379
|
+
self.client.u2.click(width // 2, 100)
|
|
2380
|
+
return {"success": True, "message": "✅ 键盘已收起 (Android - 点击空白区域)"}
|
|
2381
|
+
else:
|
|
2382
|
+
return {"success": True, "message": "💡 键盘未显示,无需收起"}
|
|
2383
|
+
except Exception as e:
|
|
2384
|
+
# 备用方案:直接按返回键
|
|
2385
|
+
self.client.u2.shell('input keyevent 4')
|
|
2386
|
+
return {"success": True, "message": f"✅ 键盘收起完成 (Android - back键,备用方案: {e})"}
|
|
2387
|
+
except Exception as e:
|
|
2388
|
+
return {"success": False, "message": f"❌ 收起键盘失败: {e}"}
|
|
2389
|
+
|
|
2313
2390
|
def wait(self, seconds: float) -> Dict:
|
|
2314
2391
|
"""等待指定时间"""
|
|
2315
2392
|
time.sleep(seconds)
|
|
@@ -594,6 +594,12 @@ class MobileMCPServer:
|
|
|
594
594
|
}
|
|
595
595
|
))
|
|
596
596
|
|
|
597
|
+
tools.append(Tool(
|
|
598
|
+
name="mobile_hide_keyboard",
|
|
599
|
+
description="⌨️ 收起键盘。【重要】输入密码后勾选协议前必须调用,确保复选框不被键盘遮挡。登录场景必备!",
|
|
600
|
+
inputSchema={"type": "object", "properties": {}, "required": []}
|
|
601
|
+
))
|
|
602
|
+
|
|
597
603
|
# ==================== 应用管理 ====================
|
|
598
604
|
tools.append(Tool(
|
|
599
605
|
name="mobile_launch_app",
|
|
@@ -1040,6 +1046,10 @@ class MobileMCPServer:
|
|
|
1040
1046
|
result = self.tools.wait(arguments["seconds"])
|
|
1041
1047
|
return [TextContent(type="text", text=self.format_response(result))]
|
|
1042
1048
|
|
|
1049
|
+
elif name == "mobile_hide_keyboard":
|
|
1050
|
+
result = await self.tools.hide_keyboard()
|
|
1051
|
+
return [TextContent(type="text", text=self.format_response(result))]
|
|
1052
|
+
|
|
1043
1053
|
# 应用管理
|
|
1044
1054
|
elif name == "mobile_launch_app":
|
|
1045
1055
|
result = await self.tools.launch_app(arguments["package_name"])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mobile-mcp-ai
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.12
|
|
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
|
|
@@ -460,6 +460,7 @@ pip install pyautogui pyperclip pygetwindow
|
|
|
460
460
|
| 👆 | `mobile_swipe` | 滑动 |
|
|
461
461
|
| ⌨️ | `mobile_press_key` | 按键 |
|
|
462
462
|
| ⏱️ | `mobile_wait` | 等待 |
|
|
463
|
+
| ⌨️ | `mobile_hide_keyboard` | 收起键盘(登录场景必备) |
|
|
463
464
|
| 📦 | `mobile_launch_app` | 启动应用 |
|
|
464
465
|
| 📦 | `mobile_terminate_app` | 终止应用 |
|
|
465
466
|
| 📦 | `mobile_list_apps` | 列出应用 |
|
|
@@ -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.7.
|
|
28
|
+
version="2.7.12", # 新增 hide_keyboard 工具,用于登录场景收起键盘确保协议复选框可点击
|
|
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
|
{mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/templates/close_buttons/auto_x_0112_151217.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/templates/close_buttons/auto_x_0112_152037.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/templates/close_buttons/auto_x_0112_152840.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/templates/close_buttons/auto_x_0112_153256.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/core/templates/close_buttons/auto_x_0112_154847.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/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
|
{mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/templates/close_buttons/auto_x_0112_151217.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/templates/close_buttons/auto_x_0112_152037.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/templates/close_buttons/auto_x_0112_152840.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/templates/close_buttons/auto_x_0112_153256.png
RENAMED
|
File without changes
|
{mobile_mcp_ai-2.7.11 → mobile_mcp_ai-2.7.12}/templates/close_buttons/auto_x_0112_154847.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|