mobile-mcp-ai 2.2.6__py3-none-any.whl → 2.5.3__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.
- mobile_mcp/config.py +3 -2
- mobile_mcp/core/basic_tools_lite.py +3193 -0
- mobile_mcp/core/ios_client_wda.py +569 -0
- mobile_mcp/core/ios_device_manager_wda.py +306 -0
- mobile_mcp/core/mobile_client.py +246 -20
- mobile_mcp/core/template_matcher.py +429 -0
- mobile_mcp/core/templates/close_buttons/auto_x_0112_151217.png +0 -0
- mobile_mcp/core/templates/close_buttons/auto_x_0112_152037.png +0 -0
- mobile_mcp/core/templates/close_buttons/auto_x_0112_152840.png +0 -0
- mobile_mcp/core/templates/close_buttons/auto_x_0112_153256.png +0 -0
- mobile_mcp/core/templates/close_buttons/auto_x_0112_154847.png +0 -0
- mobile_mcp/core/templates/close_buttons/gray_x_stock_ad.png +0 -0
- mobile_mcp/mcp_tools/__init__.py +10 -0
- mobile_mcp/mcp_tools/mcp_server.py +992 -0
- mobile_mcp_ai-2.5.3.dist-info/METADATA +456 -0
- mobile_mcp_ai-2.5.3.dist-info/RECORD +32 -0
- mobile_mcp_ai-2.5.3.dist-info/entry_points.txt +2 -0
- mobile_mcp/core/ai/__init__.py +0 -11
- mobile_mcp/core/ai/ai_analyzer.py +0 -197
- mobile_mcp/core/ai/ai_config.py +0 -116
- mobile_mcp/core/ai/ai_platform_adapter.py +0 -399
- mobile_mcp/core/ai/smart_test_executor.py +0 -520
- mobile_mcp/core/ai/test_generator.py +0 -365
- mobile_mcp/core/ai/test_generator_from_history.py +0 -391
- mobile_mcp/core/ai/test_generator_standalone.py +0 -293
- mobile_mcp/core/assertion/__init__.py +0 -9
- mobile_mcp/core/assertion/smart_assertion.py +0 -341
- mobile_mcp/core/basic_tools.py +0 -945
- mobile_mcp/core/h5/__init__.py +0 -10
- mobile_mcp/core/h5/h5_handler.py +0 -548
- mobile_mcp/core/ios_client.py +0 -219
- mobile_mcp/core/ios_device_manager.py +0 -252
- mobile_mcp/core/locator/__init__.py +0 -10
- mobile_mcp/core/locator/cursor_ai_auto_analyzer.py +0 -119
- mobile_mcp/core/locator/cursor_vision_helper.py +0 -414
- mobile_mcp/core/locator/mobile_smart_locator.py +0 -1747
- mobile_mcp/core/locator/position_analyzer.py +0 -813
- mobile_mcp/core/locator/script_updater.py +0 -157
- mobile_mcp/core/nl_test_runner.py +0 -585
- mobile_mcp/core/smart_app_launcher.py +0 -421
- mobile_mcp/core/smart_tools.py +0 -311
- mobile_mcp/mcp/__init__.py +0 -13
- mobile_mcp/mcp/mcp_server.py +0 -1126
- mobile_mcp/mcp/mcp_server_simple.py +0 -23
- mobile_mcp/vision/__init__.py +0 -10
- mobile_mcp/vision/vision_locator.py +0 -405
- mobile_mcp_ai-2.2.6.dist-info/METADATA +0 -503
- mobile_mcp_ai-2.2.6.dist-info/RECORD +0 -49
- mobile_mcp_ai-2.2.6.dist-info/entry_points.txt +0 -2
- {mobile_mcp_ai-2.2.6.dist-info → mobile_mcp_ai-2.5.3.dist-info}/WHEEL +0 -0
- {mobile_mcp_ai-2.2.6.dist-info → mobile_mcp_ai-2.5.3.dist-info}/licenses/LICENSE +0 -0
- {mobile_mcp_ai-2.2.6.dist-info → mobile_mcp_ai-2.5.3.dist-info}/top_level.txt +0 -0
|
@@ -1,421 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
"""
|
|
4
|
-
智能App启动器 - 处理广告、弹窗、加载等待
|
|
5
|
-
"""
|
|
6
|
-
import asyncio
|
|
7
|
-
import sys
|
|
8
|
-
from typing import Dict, Optional
|
|
9
|
-
|
|
10
|
-
from .dynamic_config import DynamicConfig
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class SmartAppLauncher:
|
|
14
|
-
"""
|
|
15
|
-
智能App启动器
|
|
16
|
-
|
|
17
|
-
功能:
|
|
18
|
-
1. 启动App后智能等待主页加载
|
|
19
|
-
2. 自动检测并关闭广告/弹窗
|
|
20
|
-
3. 等待网络加载完成
|
|
21
|
-
4. 智能判断是否进入主页
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
def __init__(self, mobile_client):
|
|
25
|
-
"""
|
|
26
|
-
初始化智能启动器
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
mobile_client: MobileClient实例
|
|
30
|
-
"""
|
|
31
|
-
self.client = mobile_client
|
|
32
|
-
|
|
33
|
-
# 常见的广告/弹窗关闭按钮特征
|
|
34
|
-
self.ad_close_keywords = [
|
|
35
|
-
'跳过', '关闭', '×', 'X', 'x', '✕',
|
|
36
|
-
'skip', 'close', '稍后', '取消',
|
|
37
|
-
'我知道了', '不再提示', '下次再说',
|
|
38
|
-
'暂不', '以后再说', '返回'
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
# 常见的弹窗容器特征
|
|
42
|
-
self.popup_keywords = [
|
|
43
|
-
'dialog', 'popup', 'alert', 'modal',
|
|
44
|
-
'弹窗', '对话框', '提示'
|
|
45
|
-
]
|
|
46
|
-
|
|
47
|
-
async def launch_with_smart_wait(
|
|
48
|
-
self,
|
|
49
|
-
package_name: str,
|
|
50
|
-
max_wait: int = 3, # 优化:最多等待3秒(快速启动)
|
|
51
|
-
auto_close_ads: bool = True
|
|
52
|
-
) -> Dict:
|
|
53
|
-
"""
|
|
54
|
-
智能启动App并等待主页加载
|
|
55
|
-
|
|
56
|
-
Args:
|
|
57
|
-
package_name: App包名
|
|
58
|
-
max_wait: 最大等待时间(秒,默认3秒 - 快速模式)
|
|
59
|
-
auto_close_ads: 是否自动关闭广告/弹窗
|
|
60
|
-
|
|
61
|
-
Returns:
|
|
62
|
-
启动结果
|
|
63
|
-
"""
|
|
64
|
-
print(f"\n🚀 智能启动App: {package_name}", file=sys.stderr)
|
|
65
|
-
|
|
66
|
-
try:
|
|
67
|
-
# 🎯 启动前:强制恢复竖屏(防止上次横屏残留)
|
|
68
|
-
print(f" 🔄 检查屏幕方向...", file=sys.stderr)
|
|
69
|
-
self.client.force_portrait()
|
|
70
|
-
|
|
71
|
-
# 1. 启动App
|
|
72
|
-
print(f" 📱 正在启动...", file=sys.stderr)
|
|
73
|
-
self.client.u2.app_start(package_name)
|
|
74
|
-
await asyncio.sleep(1) # 等待App进程启动
|
|
75
|
-
|
|
76
|
-
# 🎯 启动后:再次强制竖屏(防止App启动时强制横屏)
|
|
77
|
-
self.client.force_portrait()
|
|
78
|
-
|
|
79
|
-
# 2. 验证App是否启动
|
|
80
|
-
current_package = await self._get_current_package()
|
|
81
|
-
if current_package != package_name:
|
|
82
|
-
return {
|
|
83
|
-
"success": False,
|
|
84
|
-
"reason": f"App启动失败,当前: {current_package},期望: {package_name}"
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
print(f" ✅ App进程已启动", file=sys.stderr)
|
|
88
|
-
|
|
89
|
-
# 🎯 关键优化:确保启动后至少等待 2 秒,让界面完全渲染
|
|
90
|
-
print(f" ⏳ 等待界面渲染...", file=sys.stderr)
|
|
91
|
-
await asyncio.sleep(2) # 最小等待 2 秒
|
|
92
|
-
print(f" ✅ 已等待 2 秒,界面应已稳定", file=sys.stderr)
|
|
93
|
-
|
|
94
|
-
# 3. 快速等待并自动截图验证(新策略)
|
|
95
|
-
result = await self._wait_for_home_page_fast(
|
|
96
|
-
package_name,
|
|
97
|
-
max_wait=max_wait,
|
|
98
|
-
auto_close_ads=auto_close_ads
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
# 快速模式:总是返回成功+截图路径
|
|
102
|
-
print(f" ✅ App已启动{result['wait_time'] + 2:.1f}秒,已自动截图", file=sys.stderr)
|
|
103
|
-
return {
|
|
104
|
-
"success": True,
|
|
105
|
-
"package": package_name,
|
|
106
|
-
"wait_time": result['wait_time'],
|
|
107
|
-
"ads_closed": result['ads_closed'],
|
|
108
|
-
"screenshot_path": result.get('screenshot_path'),
|
|
109
|
-
"message": "App已启动,请查看截图确认是否进入主页"
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
except Exception as e:
|
|
113
|
-
print(f" ❌ 智能启动失败: {e}", file=sys.stderr)
|
|
114
|
-
return {
|
|
115
|
-
"success": False,
|
|
116
|
-
"reason": str(e)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async def _wait_for_home_page(
|
|
120
|
-
self,
|
|
121
|
-
package_name: str,
|
|
122
|
-
max_wait: int = 5, # 优化:从10秒减少到5秒
|
|
123
|
-
auto_close_ads: bool = True
|
|
124
|
-
) -> Dict:
|
|
125
|
-
"""
|
|
126
|
-
等待主页加载完成
|
|
127
|
-
|
|
128
|
-
策略:
|
|
129
|
-
1. 每0.5秒检查一次页面状态
|
|
130
|
-
2. 检测广告/弹窗并自动关闭
|
|
131
|
-
3. 检测页面是否稳定(元素不再变化)
|
|
132
|
-
4. 超时后返回当前状态
|
|
133
|
-
|
|
134
|
-
Returns:
|
|
135
|
-
{
|
|
136
|
-
"loaded": bool, # 是否加载完成
|
|
137
|
-
"wait_time": float, # 等待时间
|
|
138
|
-
"ads_closed": int, # 关闭的广告数
|
|
139
|
-
"popups_closed": int # 关闭的弹窗数
|
|
140
|
-
}
|
|
141
|
-
"""
|
|
142
|
-
import time
|
|
143
|
-
start_time = time.time()
|
|
144
|
-
|
|
145
|
-
ads_closed = 0
|
|
146
|
-
popups_closed = 0
|
|
147
|
-
last_snapshot = None
|
|
148
|
-
stable_count = 0 # 页面稳定计数(连续2次快照相同认为稳定)
|
|
149
|
-
|
|
150
|
-
print(f" ⏳ 等待主页加载(最多{max_wait}秒)...", file=sys.stderr)
|
|
151
|
-
|
|
152
|
-
check_interval = 0.3 # 优化:每0.3秒检查一次(更快响应)
|
|
153
|
-
max_checks = int(max_wait / check_interval)
|
|
154
|
-
|
|
155
|
-
for i in range(max_checks):
|
|
156
|
-
await asyncio.sleep(check_interval)
|
|
157
|
-
elapsed = time.time() - start_time
|
|
158
|
-
|
|
159
|
-
# 检查当前包名(防止跳转到其他App)
|
|
160
|
-
current_package = await self._get_current_package()
|
|
161
|
-
if current_package != package_name:
|
|
162
|
-
print(f" ⚠️ 检测到包名变化: {package_name} -> {current_package}", file=sys.stderr)
|
|
163
|
-
# 可能跳转到其他页面(如授权页),继续等待
|
|
164
|
-
await asyncio.sleep(1)
|
|
165
|
-
continue
|
|
166
|
-
|
|
167
|
-
# 获取页面快照
|
|
168
|
-
try:
|
|
169
|
-
snapshot = self.client.u2.dump_hierarchy()
|
|
170
|
-
|
|
171
|
-
# 1. 检测并关闭广告/弹窗
|
|
172
|
-
if auto_close_ads:
|
|
173
|
-
closed = await self._try_close_ads_and_popups(snapshot)
|
|
174
|
-
if closed:
|
|
175
|
-
ads_closed += closed
|
|
176
|
-
print(f" 🎯 已关闭 {closed} 个广告/弹窗", file=sys.stderr)
|
|
177
|
-
await asyncio.sleep(0.3) # 等待关闭动画(从0.5秒优化到0.3秒)
|
|
178
|
-
continue # 重新检查
|
|
179
|
-
|
|
180
|
-
# 2. 检测页面是否稳定
|
|
181
|
-
if last_snapshot and snapshot == last_snapshot:
|
|
182
|
-
stable_count += 1
|
|
183
|
-
if stable_count >= 2:
|
|
184
|
-
# 页面已稳定(连续2次快照相同)
|
|
185
|
-
print(f" ✅ 页面稳定,加载完成(耗时{elapsed:.1f}秒)", file=sys.stderr)
|
|
186
|
-
return {
|
|
187
|
-
"loaded": True,
|
|
188
|
-
"wait_time": elapsed,
|
|
189
|
-
"ads_closed": ads_closed,
|
|
190
|
-
"popups_closed": popups_closed
|
|
191
|
-
}
|
|
192
|
-
else:
|
|
193
|
-
stable_count = 0
|
|
194
|
-
|
|
195
|
-
last_snapshot = snapshot
|
|
196
|
-
|
|
197
|
-
# 优化:每1.5秒打印一次等待进度(从2秒减少)
|
|
198
|
-
if i % 5 == 0 and i > 0: # 5 * 0.3秒 = 1.5秒
|
|
199
|
-
print(f" ⏳ 等待中... ({elapsed:.1f}秒)", file=sys.stderr)
|
|
200
|
-
|
|
201
|
-
except Exception as e:
|
|
202
|
-
print(f" ⚠️ 检查页面状态失败: {e}", file=sys.stderr)
|
|
203
|
-
continue
|
|
204
|
-
|
|
205
|
-
# 超时
|
|
206
|
-
elapsed = time.time() - start_time
|
|
207
|
-
print(f" ⏰ 等待超时({elapsed:.1f}秒),但App已启动", file=sys.stderr)
|
|
208
|
-
return {
|
|
209
|
-
"loaded": False,
|
|
210
|
-
"wait_time": elapsed,
|
|
211
|
-
"ads_closed": ads_closed,
|
|
212
|
-
"popups_closed": popups_closed
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async def _try_close_ads_and_popups(self, snapshot: str) -> int:
|
|
216
|
-
"""
|
|
217
|
-
尝试关闭广告和弹窗(更谨慎的检测逻辑)
|
|
218
|
-
|
|
219
|
-
Args:
|
|
220
|
-
snapshot: 页面XML快照
|
|
221
|
-
|
|
222
|
-
Returns:
|
|
223
|
-
关闭的数量
|
|
224
|
-
"""
|
|
225
|
-
closed_count = 0
|
|
226
|
-
|
|
227
|
-
try:
|
|
228
|
-
# 解析XML查找关闭按钮
|
|
229
|
-
elements = self.client.xml_parser.parse(snapshot)
|
|
230
|
-
|
|
231
|
-
# 🎯 改进:先检测是否有弹窗容器(避免误点击正常UI)
|
|
232
|
-
has_popup = False
|
|
233
|
-
for elem in elements:
|
|
234
|
-
class_name = elem.get('class', '').lower()
|
|
235
|
-
resource_id = elem.get('resource_id', '').lower()
|
|
236
|
-
# 检查是否是弹窗容器
|
|
237
|
-
if any(keyword in class_name or keyword in resource_id
|
|
238
|
-
for keyword in ['dialog', 'popup', 'alert', 'modal']):
|
|
239
|
-
has_popup = True
|
|
240
|
-
break
|
|
241
|
-
|
|
242
|
-
# 如果没有检测到弹窗容器,不执行关闭操作(避免误点击)
|
|
243
|
-
if not has_popup:
|
|
244
|
-
return 0
|
|
245
|
-
|
|
246
|
-
# 查找可能的关闭按钮
|
|
247
|
-
close_buttons = []
|
|
248
|
-
|
|
249
|
-
for elem in elements:
|
|
250
|
-
if not elem.get('clickable', False):
|
|
251
|
-
continue
|
|
252
|
-
|
|
253
|
-
text = elem.get('text', '').lower()
|
|
254
|
-
content_desc = elem.get('content_desc', '').lower()
|
|
255
|
-
resource_id = elem.get('resource_id', '').lower()
|
|
256
|
-
bounds = elem.get('bounds', '')
|
|
257
|
-
|
|
258
|
-
# 检查是否是关闭按钮
|
|
259
|
-
is_close_button = False
|
|
260
|
-
for keyword in self.ad_close_keywords:
|
|
261
|
-
keyword_lower = keyword.lower()
|
|
262
|
-
if (keyword_lower in text or
|
|
263
|
-
keyword_lower in content_desc or
|
|
264
|
-
keyword_lower in resource_id or
|
|
265
|
-
('close' in resource_id and 'btn' in resource_id) or
|
|
266
|
-
('skip' in resource_id)):
|
|
267
|
-
is_close_button = True
|
|
268
|
-
break
|
|
269
|
-
|
|
270
|
-
# 🎯 改进:优先选择右上角的关闭按钮(更可能是真正的关闭按钮)
|
|
271
|
-
if is_close_button and bounds:
|
|
272
|
-
import re
|
|
273
|
-
match = re.search(r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]', bounds)
|
|
274
|
-
if match:
|
|
275
|
-
x1, y1, x2, y2 = map(int, match.groups())
|
|
276
|
-
# 计算元素位置(右上角区域优先级更高)
|
|
277
|
-
elem['_priority'] = 0
|
|
278
|
-
if x1 > 800: # 右侧
|
|
279
|
-
elem['_priority'] += 2
|
|
280
|
-
if y1 < 500: # 上部
|
|
281
|
-
elem['_priority'] += 2
|
|
282
|
-
close_buttons.append(elem)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
# 🎯 改进:按优先级排序(右上角的关闭按钮优先)
|
|
286
|
-
close_buttons.sort(key=lambda x: x.get('_priority', 0), reverse=True)
|
|
287
|
-
|
|
288
|
-
# 尝试点击关闭按钮(使用动态配置的最大数量,避免误点击)
|
|
289
|
-
max_buttons = DynamicConfig.max_close_buttons
|
|
290
|
-
for button in close_buttons[:max_buttons]:
|
|
291
|
-
try:
|
|
292
|
-
# 优先使用bounds点击(更可靠)
|
|
293
|
-
bounds = button.get('bounds', '')
|
|
294
|
-
if bounds:
|
|
295
|
-
# 解析bounds并点击中心点
|
|
296
|
-
import re
|
|
297
|
-
match = re.search(r'\[(\d+),(\d+)\]\[(\d+),(\d+)\]', bounds)
|
|
298
|
-
if match:
|
|
299
|
-
x1, y1, x2, y2 = map(int, match.groups())
|
|
300
|
-
center_x = (x1 + x2) // 2
|
|
301
|
-
center_y = (y1 + y2) // 2
|
|
302
|
-
|
|
303
|
-
button_desc = button.get('text') or button.get('content_desc') or '未知'
|
|
304
|
-
print(f" 🎯 检测到弹窗,点击关闭按钮: {button_desc} (位置: {center_x}, {center_y})", file=sys.stderr)
|
|
305
|
-
|
|
306
|
-
# 使用动态配置的等待时间
|
|
307
|
-
await asyncio.sleep(DynamicConfig.wait_before_close_ad)
|
|
308
|
-
|
|
309
|
-
self.client.u2.click(center_x, center_y)
|
|
310
|
-
closed_count += 1
|
|
311
|
-
|
|
312
|
-
print(f" ✅ 已关闭弹窗: {button_desc}", file=sys.stderr)
|
|
313
|
-
|
|
314
|
-
# 等待关闭动画(使用动态配置)
|
|
315
|
-
await asyncio.sleep(DynamicConfig.wait_after_click)
|
|
316
|
-
|
|
317
|
-
except Exception as e:
|
|
318
|
-
print(f" ⚠️ 点击关闭按钮失败: {e}", file=sys.stderr)
|
|
319
|
-
continue
|
|
320
|
-
|
|
321
|
-
return closed_count
|
|
322
|
-
|
|
323
|
-
except Exception as e:
|
|
324
|
-
print(f" ⚠️ 关闭广告/弹窗失败: {e}", file=sys.stderr)
|
|
325
|
-
return 0
|
|
326
|
-
|
|
327
|
-
async def _wait_for_home_page_fast(
|
|
328
|
-
self,
|
|
329
|
-
package_name: str,
|
|
330
|
-
max_wait: int = 3, # 快速模式:最多3秒
|
|
331
|
-
auto_close_ads: bool = True
|
|
332
|
-
) -> Dict:
|
|
333
|
-
"""
|
|
334
|
-
快速启动模式:等待短时间后立即截图,让Cursor AI判断是否已进入主页
|
|
335
|
-
|
|
336
|
-
策略:
|
|
337
|
-
1. 等待2秒(让App基本启动)
|
|
338
|
-
2. 期间检测并关闭广告/弹窗
|
|
339
|
-
3. 等待结束后立即截图
|
|
340
|
-
4. 提示用户通过Cursor AI验证截图
|
|
341
|
-
|
|
342
|
-
Returns:
|
|
343
|
-
{
|
|
344
|
-
"loaded": bool, # 是否加载完成
|
|
345
|
-
"wait_time": float, # 等待时间
|
|
346
|
-
"ads_closed": int, # 关闭的广告数
|
|
347
|
-
"screenshot_path": str # 截图路径(供AI验证)
|
|
348
|
-
}
|
|
349
|
-
"""
|
|
350
|
-
import time
|
|
351
|
-
start_time = time.time()
|
|
352
|
-
|
|
353
|
-
ads_closed = 0
|
|
354
|
-
screenshot_path = None
|
|
355
|
-
|
|
356
|
-
print(f" ⏳ 快速启动模式:等待{max_wait}秒并自动截图...", file=sys.stderr)
|
|
357
|
-
|
|
358
|
-
# 快速检测广告/弹窗(最多检查3次,每次1秒)
|
|
359
|
-
for i in range(min(max_wait, 3)):
|
|
360
|
-
await asyncio.sleep(1)
|
|
361
|
-
elapsed = time.time() - start_time
|
|
362
|
-
|
|
363
|
-
# 检查当前包名
|
|
364
|
-
current_package = await self._get_current_package()
|
|
365
|
-
if current_package != package_name:
|
|
366
|
-
print(f" ⚠️ 检测到包名变化: {package_name} -> {current_package}", file=sys.stderr)
|
|
367
|
-
await asyncio.sleep(0.5)
|
|
368
|
-
continue
|
|
369
|
-
|
|
370
|
-
# 检测并关闭广告/弹窗
|
|
371
|
-
if auto_close_ads:
|
|
372
|
-
try:
|
|
373
|
-
snapshot = self.client.u2.dump_hierarchy()
|
|
374
|
-
closed = await self._try_close_ads_and_popups(snapshot)
|
|
375
|
-
if closed:
|
|
376
|
-
ads_closed += closed
|
|
377
|
-
print(f" 🎯 已关闭 {closed} 个广告/弹窗", file=sys.stderr)
|
|
378
|
-
except Exception as e:
|
|
379
|
-
print(f" ⚠️ 检查弹窗失败: {e}", file=sys.stderr)
|
|
380
|
-
|
|
381
|
-
print(f" ⏳ 已等待{int(elapsed)}秒...", file=sys.stderr)
|
|
382
|
-
|
|
383
|
-
# 等待结束,立即截图
|
|
384
|
-
elapsed = time.time() - start_time
|
|
385
|
-
print(f" 📸 App已启动{elapsed:.1f}秒,正在截图供AI验证...", file=sys.stderr)
|
|
386
|
-
|
|
387
|
-
try:
|
|
388
|
-
import re
|
|
389
|
-
timestamp = time.strftime("%Y%m%d_%H%M%S")
|
|
390
|
-
from pathlib import Path
|
|
391
|
-
project_root = Path(__file__).parent.parent
|
|
392
|
-
screenshot_dir = project_root / "screenshots"
|
|
393
|
-
screenshot_dir.mkdir(exist_ok=True)
|
|
394
|
-
|
|
395
|
-
# 生成截图文件名
|
|
396
|
-
safe_package = re.sub(r'[^\w\s-]', '', package_name).strip()
|
|
397
|
-
filename = f"app_launch_{safe_package}_{timestamp}.png"
|
|
398
|
-
screenshot_path = screenshot_dir / filename
|
|
399
|
-
|
|
400
|
-
# 截图
|
|
401
|
-
self.client.u2.screenshot(str(screenshot_path))
|
|
402
|
-
print(f" ✅ 截图已保存: {screenshot_path}", file=sys.stderr)
|
|
403
|
-
print(f" 💡 提示:请查看截图,确认是否已进入主页", file=sys.stderr)
|
|
404
|
-
|
|
405
|
-
except Exception as e:
|
|
406
|
-
print(f" ⚠️ 截图失败: {e}", file=sys.stderr)
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
"wait_time": elapsed,
|
|
410
|
-
"ads_closed": ads_closed,
|
|
411
|
-
"screenshot_path": str(screenshot_path) if screenshot_path else None
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
async def _get_current_package(self) -> Optional[str]:
|
|
415
|
-
"""获取当前包名"""
|
|
416
|
-
try:
|
|
417
|
-
info = self.client.u2.app_current()
|
|
418
|
-
return info.get('package')
|
|
419
|
-
except:
|
|
420
|
-
return None
|
|
421
|
-
|