Repository navigation
Expand file tree
/
Copy pathconftest.py
More file actions
208 lines (168 loc) · 6.82 KB
/
Copy pathconftest.py
File metadata and controls
208 lines (168 loc) · 6.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
"""
pytest配置文件和全局fixture
"""
import pytest
import os
import time
from pathlib import Path
from airtest.core.api import *
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
from poco.drivers.unity3d import UnityPoco
from config.test_config import Config
@pytest.fixture(scope="session")
def setup_test_environment():
"""设置测试环境"""
# 创建必要的目录
os.makedirs("reports", exist_ok=True)
os.makedirs("reports/screenshots", exist_ok=True)
os.makedirs("logs", exist_ok=True)
# 设置容器环境标记
os.environ.setdefault('CONTAINER_MODE', 'true')
yield
@pytest.fixture(scope="session")
def global_device_setup(setup_test_environment):
"""全局设备连接设置"""
max_retries = 3
retry_delay = 3
device_uri = Config.get_device_uri()
print(f"\n=== 设备连接信息 ===")
print(f"Device URI: {device_uri}")
print(f"ADB_SERVER_SOCKET: {os.getenv('ADB_SERVER_SOCKET', '未设置')}")
print(f"容器模式: {Config.CONTAINER_MODE}")
for attempt in range(max_retries):
try:
print(f"\n尝试连接设备 ({attempt + 1}/{max_retries})...")
auto_setup(__file__, logdir=True, devices=[device_uri])
device_obj = device()
if device_obj:
display_info = device_obj.get_display_info()
print(f"✅ 设备连接成功! 屏幕: {display_info}")
yield device_obj
# 测试结束后主动清理,避免 atexit 时 logging stream 已关闭导致报错
# Airtest 通过 monkey-patch threading._shutdown 注册了 cleanup
# 在 pytest 退出前先执行,此时 logging stream 还可用
from airtest.core.helper import G
from airtest.utils.snippet import CLEANUP_CALLS
for dev in G.DEVICE_LIST:
try:
dev.disconnect()
except Exception:
pass
G.DEVICE_LIST = []
G.DEVICE = None
# 清空 cleanup 队列,防止 threading._shutdown 时重复执行报错
while not CLEANUP_CALLS.empty():
try:
func, args, kwargs = CLEANUP_CALLS.get_nowait()
func(*args, **kwargs)
except Exception:
pass
return
else:
raise Exception("设备对象为空")
except Exception as e:
print(f"⚠️ 第{attempt + 1}次连接失败: {e}")
if attempt < max_retries - 1:
sleep(retry_delay)
else:
print(f"\n❌ 设备连接最终失败: {e}")
print("排查步骤:")
print(f" 1. 宿主机执行: adb devices (确认设备在线)")
print(f" 2. 确认 ADB_SERVER_SOCKET 环境变量已设置")
print(f" 3. 确认 DEVICE_HOST 指向宿主机 (macOS用host.docker.internal)")
raise
@pytest.fixture(scope="class")
def calculator_setup(global_device_setup):
"""计算器应用设置"""
device_obj = global_device_setup
calculator_pkg = "com.google.android.calculator"
calculator_apk = "demo_apps/com.google.android.calculator.apk"
try:
# 检查并安装应用
if calculator_pkg not in device_obj.list_app():
apk_path = Path(calculator_apk)
if apk_path.exists():
device_obj.install_app(str(apk_path))
# 启动应用
stop_app(calculator_pkg)
start_app(calculator_pkg)
sleep(2)
# 初始化Poco
poco = AndroidUiautomationPoco()
yield device_obj, poco
except Exception as e:
print(f"❌ 计算器应用设置失败: {e}")
raise
finally:
try:
stop_app(calculator_pkg)
except:
pass
@pytest.fixture(scope="class")
def unity_setup(global_device_setup):
"""Unity应用设置"""
device_obj = global_device_setup
unity_pkg = "com.NetEase.PocoDemo"
try:
# 启动Unity应用
stop_app(unity_pkg)
start_app(unity_pkg)
sleep(5)
# 初始化Unity Poco
poco = UnityPoco()
yield device_obj, poco
except Exception as e:
print(f"❌ Unity应用设置失败: {e}")
raise
finally:
try:
stop_app(unity_pkg)
except:
pass
@pytest.fixture(autouse=True)
def screenshot_on_failure(request):
"""测试失败时自动截图"""
yield
if hasattr(request.node, 'rep_call') and request.node.rep_call.failed:
if Config.SCREENSHOT_ON_FAILURE:
test_name = request.node.name.replace("::", "_").replace("[", "_").replace("]", "_")
timestamp = int(time.time())
screenshot_name = f"failure_{test_name}_{timestamp}.png"
screenshot_path = os.path.join("reports/screenshots", screenshot_name)
try:
device_obj = device()
if device_obj:
snapshot(screenshot_path)
except Exception:
pass
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
"""生成测试报告钩子"""
outcome = yield
rep = outcome.get_result()
setattr(item, "rep_" + rep.when, rep)
def pytest_configure(config):
"""pytest配置钩子"""
# 添加自定义标记
config.addinivalue_line("markers", "cloud: 云平台专用测试")
config.addinivalue_line("markers", "local: 本地设备专用测试")
config.addinivalue_line("markers", "retry: 支持重试的测试")
config.addinivalue_line("markers", "smoke: 冒烟测试")
config.addinivalue_line("markers", "regression: 回归测试")
config.addinivalue_line("markers", "ui: UI测试")
config.addinivalue_line("markers", "stress: 压力测试")
config.addinivalue_line("markers", "performance: 性能测试")
def pytest_collection_modifyitems(config, items):
"""修改测试收集 - 根据环境跳过不适用的测试"""
if Config.is_cloud_platform():
# 云平台环境,跳过本地专用测试
skip_local = pytest.mark.skip(reason="云平台环境跳过本地专用测试")
for item in items:
if "local" in item.keywords:
item.add_marker(skip_local)
else:
# 本地环境,跳过云平台专用测试
skip_cloud = pytest.mark.skip(reason="本地环境跳过云平台专用测试")
for item in items:
if "cloud" in item.keywords:
item.add_marker(skip_cloud)