base_test.py
9.83 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
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
"""
基础测试类 - 提供通用的浏览器自动化测试功能
Python 3.7.8 兼容
"""
import os
import time
from datetime import datetime
from typing import Optional, Dict, Any
from playwright.sync_api import sync_playwright, Page, Browser, BrowserContext
class BaseBrowserTest:
"""浏览器自动化测试基类"""
def __init__(self, headless: bool = False, slow_mo: int = 100):
"""
初始化浏览器测试
Args:
headless: 是否无头模式运行
slow_mo: 操作间隔时间(毫秒)
"""
self.headless = headless
self.slow_mo = slow_mo
self.playwright = None
self.browser: Optional[Browser] = None
self.context: Optional[BrowserContext] = None
self.page: Optional[Page] = None
# 测试配置
self.base_url = os.environ.get('FRONTEND_URL', 'http://localhost:3001')
self.screenshots_dir = os.path.join(os.path.dirname(__file__), 'screenshots')
self.reports_dir = os.path.join(os.path.dirname(__file__), 'reports')
# 创建必要的目录
os.makedirs(self.screenshots_dir, exist_ok=True)
os.makedirs(self.reports_dir, exist_ok=True)
def setup_browser(self) -> None:
"""启动浏览器"""
self.playwright = sync_playwright().start()
# 启动浏览器(使用Chromium)
self.browser = self.playwright.chromium.launch(
headless=self.headless,
slow_mo=self.slow_mo,
args=['--no-sandbox', '--disable-dev-shm-usage']
)
# 创建浏览器上下文
self.context = self.browser.new_context(
viewport={'width': 1920, 'height': 1080},
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
)
# 创建新页面
self.page = self.context.new_page()
# 设置默认超时时间
self.page.set_default_timeout(30000) # 30秒
def teardown_browser(self) -> None:
"""关闭浏览器"""
if self.page:
self.page.close()
if self.context:
self.context.close()
if self.browser:
self.browser.close()
if self.playwright:
self.playwright.stop()
def take_screenshot(self, name: str = None) -> str:
"""
截图
Args:
name: 截图文件名(不包含扩展名)
Returns:
截图文件路径
"""
if not name:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
name = f'screenshot_{timestamp}'
screenshot_path = os.path.join(self.screenshots_dir, f'{name}.png')
self.page.screenshot(path=screenshot_path, full_page=True)
print(f"截图已保存: {screenshot_path}")
return screenshot_path
def wait_for_element(self, selector: str, timeout: int = 10000) -> None:
"""
等待元素出现
Args:
selector: CSS选择器
timeout: 超时时间(毫秒)
"""
self.page.wait_for_selector(selector, timeout=timeout)
def wait_for_text(self, text: str, timeout: int = 10000) -> None:
"""
等待文本出现
Args:
text: 要等待的文本
timeout: 超时时间(毫秒)
"""
self.page.wait_for_selector(f"text={text}", timeout=timeout)
def fill_input(self, selector: str, value: str) -> None:
"""
填充输入框
Args:
selector: CSS选择器
value: 要输入的值
"""
self.page.fill(selector, value)
def click_element(self, selector: str) -> None:
"""
点击元素
Args:
selector: CSS选择器
"""
self.page.click(selector)
def select_option(self, selector: str, value: str) -> None:
"""
选择下拉框选项
Args:
selector: CSS选择器
value: 选项值
"""
self.page.select_option(selector, value)
def get_text(self, selector: str) -> str:
"""
获取元素文本
Args:
selector: CSS选择器
Returns:
元素文本内容
"""
return self.page.text_content(selector)
def get_element_count(self, selector: str) -> int:
"""
获取元素数量
Args:
selector: CSS选择器
Returns:
元素数量
"""
return self.page.locator(selector).count()
def is_element_visible(self, selector: str) -> bool:
"""
检查元素是否可见
Args:
selector: CSS选择器
Returns:
元素是否可见
"""
return self.page.is_visible(selector)
def navigate_to(self, url: str) -> None:
"""
导航到指定URL
Args:
url: 目标URL
"""
self.page.goto(url)
self.page.wait_for_load_state('networkidle')
def login(self, username: str = 'admin', password: str = 'password') -> bool:
"""
登录系统
Args:
username: 用户名
password: 密码
Returns:
登录是否成功
"""
try:
# 导航到登录页面
self.navigate_to(self.base_url)
# 等待登录表单加载
self.wait_for_element('input[placeholder="请输入用户名"]')
# 填写用户名
self.fill_input('input[placeholder="请输入用户名"]', username)
# 填写密码
self.fill_input('input[placeholder="请输入密码"]', password)
# 点击登录按钮
self.click_element('button:has-text("登录")')
# 等待登录完成(检查是否跳转到首页或出现错误)
try:
# 等待页面跳转或出现成功/失败提示
self.page.wait_for_load_state('networkidle', timeout=10000)
# 检查是否成功登录(通过URL或页面元素判断)
current_url = self.page.url
# 等待页面完全加载
self.page.wait_for_timeout(2000)
# 检查是否有错误提示
error_indicators = [
'text=用户名或密码错误',
'text=登录失败',
'text=Invalid',
'text=Error',
'.error',
'.alert-danger'
]
has_error = False
for indicator in error_indicators:
if self.is_element_visible(indicator):
has_error = True
print(f"发现错误提示: {indicator}")
break
# 检查是否跳转到其他页面(表示登录成功)
if has_error:
print(f"登录失败,发现错误提示,当前URL: {current_url}")
return False
elif 'dashboard' in current_url or 'main' in current_url:
print(f"登录成功,当前URL: {current_url}")
return True
elif current_url == self.base_url or current_url == f'{self.base_url}/':
print(f"登录失败,仍在登录页面,当前URL: {current_url}")
return False
else:
print(f"登录状态不明确,当前URL: {current_url}")
return False
except Exception as e:
print(f"登录等待超时: {e}")
return False
except Exception as e:
print(f"登录过程出错: {e}")
self.take_screenshot('login_error')
return False
def run_test(self, test_name: str, test_func) -> Dict[str, Any]:
"""
运行测试并生成报告
Args:
test_name: 测试名称
test_func: 测试函数
Returns:
测试结果字典
"""
start_time = datetime.now()
result = {
'test_name': test_name,
'start_time': start_time.isoformat(),
'success': False,
'error': None,
'screenshots': [],
'duration': 0
}
try:
print(f"\n{'='*50}")
print(f"开始测试: {test_name}")
print(f"{'='*50}")
# 设置浏览器
self.setup_browser()
# 执行测试
test_func()
result['success'] = True
print(f"✅ 测试通过: {test_name}")
except Exception as e:
result['error'] = str(e)
print(f"❌ 测试失败: {test_name}")
print(f"错误信息: {e}")
# 失败时截图
screenshot_path = self.take_screenshot(f'{test_name}_error')
result['screenshots'].append(screenshot_path)
finally:
# 清理资源
self.teardown_browser()
# 计算耗时
end_time = datetime.now()
result['end_time'] = end_time.isoformat()
result['duration'] = (end_time - start_time).total_seconds()
print(f"测试耗时: {result['duration']:.2f}秒")
return result