zhouhui.jiang

添加出口自动化

...@@ -727,6 +727,23 @@ const formatCurrency = (amount: number) => { ...@@ -727,6 +727,23 @@ const formatCurrency = (amount: number) => {
727 return '¥' + amount.toFixed(2) 727 return '¥' + amount.toFixed(2)
728 } 728 }
729 729
730 +// 格式化日期时间为后端需要的格式 (YYYY-MM-DD HH:MM:SS)
731 +const formatDateTimeForBackend = (dateTimeString: string) => {
732 + if (!dateTimeString) return ''
733 +
734 + if (dateTimeString.includes('T')) {
735 + // 将 YYYY-MM-DDTHH:MM 格式转换为 YYYY-MM-DD HH:MM:SS 格式
736 + let formatted = dateTimeString.replace('T', ' ')
737 + // 确保时间格式包含秒数,如果只有 HH:MM 则添加 :00
738 + if (formatted.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/)) {
739 + formatted += ':00'
740 + }
741 + return formatted
742 + }
743 +
744 + return dateTimeString
745 +}
746 +
730 // 消息提示 747 // 消息提示
731 const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => { 748 const showMessage = (message: string, type: 'success' | 'error' | 'warning' = 'success') => {
732 console.log(`${type}: ${message}`) 749 console.log(`${type}: ${message}`)
...@@ -1048,11 +1065,7 @@ const calculateEditDeliveryItemTotal = (index: number) => { ...@@ -1048,11 +1065,7 @@ const calculateEditDeliveryItemTotal = (index: number) => {
1048 const handleSubmitAdd = async () => { 1065 const handleSubmitAdd = async () => {
1049 try { 1066 try {
1050 // 处理日期时间格式,将datetime-local格式转换为后端需要的格式 1067 // 处理日期时间格式,将datetime-local格式转换为后端需要的格式
1051 - let formattedDeliveryDate = addFormData.deliveryDate 1068 + const formattedDeliveryDate = formatDateTimeForBackend(addFormData.deliveryDate)
1052 - if (formattedDeliveryDate && formattedDeliveryDate.includes('T')) {
1053 - // 将 YYYY-MM-DDTHH:MM:SS 格式转换为 YYYY-MM-DD HH:MM:SS 格式
1054 - formattedDeliveryDate = formattedDeliveryDate.replace('T', ' ')
1055 - }
1056 1069
1057 // 构造新增出库的请求数据 1070 // 构造新增出库的请求数据
1058 const deliveryData: DeliveryAddReq = { 1071 const deliveryData: DeliveryAddReq = {
...@@ -1102,11 +1115,7 @@ const handleSubmitAdd = async () => { ...@@ -1102,11 +1115,7 @@ const handleSubmitAdd = async () => {
1102 const handleSubmitEdit = async () => { 1115 const handleSubmitEdit = async () => {
1103 try { 1116 try {
1104 // 处理日期时间格式,将datetime-local格式转换为后端需要的格式 1117 // 处理日期时间格式,将datetime-local格式转换为后端需要的格式
1105 - let formattedDeliveryDate = editFormData.deliveryDate 1118 + const formattedDeliveryDate = formatDateTimeForBackend(editFormData.deliveryDate)
1106 - if (formattedDeliveryDate && formattedDeliveryDate.includes('T')) {
1107 - // 将 YYYY-MM-DDTHH:MM:SS 格式转换为 YYYY-MM-DD HH:MM:SS 格式
1108 - formattedDeliveryDate = formattedDeliveryDate.replace('T', ' ')
1109 - }
1110 1119
1111 // 构造编辑出库的请求数据 1120 // 构造编辑出库的请求数据
1112 const deliveryData = { 1121 const deliveryData = {
......
1 +"""
2 +出库管理功能自动化测试
3 +Python 3.7.8 兼容
4 +"""
5 +
6 +import time
7 +from datetime import datetime
8 +from base_test import BaseBrowserTest
9 +
10 +
11 +class DeliveryManagementTest(BaseBrowserTest):
12 + """出库管理测试类"""
13 +
14 + def setup_delivery_page(self):
15 + """设置出库管理页面"""
16 + # 先登录
17 + login_success = self.login('admin', 'password')
18 + if not login_success:
19 + raise Exception("登录失败,无法继续出库管理测试")
20 +
21 + # 导航到出库管理页面
22 + self.navigate_to(f'{self.base_url}/main/delivery')
23 +
24 + # 等待页面加载
25 + self.wait_for_element('.delivery-container')
26 + self.wait_for_element('.search-section')
27 +
28 + print("出库管理页面加载完成")
29 +
30 +
31 + def test_delivery_search_by_delivery_no(self):
32 + """测试按出库单编号搜索"""
33 +
34 + # 等待搜索表单加载
35 + self.wait_for_element('input[placeholder="请输入出库单编号"]')
36 +
37 + # 输入出库单编号进行搜索
38 + test_delivery_no = "DL202501270001"
39 + self.fill_input('input[placeholder="请输入出库单编号"]', test_delivery_no)
40 +
41 + # 点击搜索按钮
42 + self.click_element('button:has-text("🔍 搜索")')
43 +
44 + # 等待搜索结果
45 + self.page.wait_for_load_state('networkidle')
46 +
47 + # 截图验证搜索结果
48 + self.take_screenshot('delivery_search_by_delivery_no')
49 +
50 + print("按出库单编号搜索测试通过")
51 +
52 + def test_delivery_search_by_dealer(self):
53 + """测试按经销商搜索"""
54 +
55 + # 等待搜索表单加载
56 + self.wait_for_element('input[placeholder="请输入经销商编码"]')
57 +
58 + # 输入经销商编码
59 + self.fill_input('input[placeholder="请输入经销商编码"]', 'DL001')
60 +
61 + # 输入经销商名称
62 + self.fill_input('input[placeholder="请输入经销商名称"]', '北京经销商')
63 +
64 + # 点击搜索按钮
65 + self.click_element('button:has-text("🔍 搜索")')
66 +
67 + # 等待搜索结果
68 + self.page.wait_for_load_state('networkidle')
69 +
70 + # 截图验证搜索结果
71 + self.take_screenshot('delivery_search_by_dealer')
72 +
73 + print("按经销商搜索测试通过")
74 +
75 +
76 + def test_delivery_status_filter(self):
77 + """测试出库状态筛选"""
78 +
79 + # 测试出库状态筛选
80 + self.select_option('select:has(option[value="0"])', '0') # 未出库
81 +
82 + # 点击搜索按钮
83 + self.click_element('button:has-text("🔍 搜索")')
84 +
85 + # 等待筛选结果
86 + self.page.wait_for_load_state('networkidle')
87 +
88 + # 截图验证筛选结果
89 + self.take_screenshot('delivery_status_filter')
90 +
91 + print("出库状态筛选测试通过")
92 +
93 +
94 +
95 +
96 + def test_delivery_reset_search(self):
97 + """测试重置搜索"""
98 +
99 + # 先填写一些搜索条件
100 + self.fill_input('input[placeholder="请输入出库单编号"]', 'TEST-DL-001')
101 + self.fill_input('input[placeholder="请输入经销商编码"]', 'TEST-DL')
102 + self.select_option('select:has(option[value="1"])', '1') # 已出库
103 +
104 + # 点击重置按钮
105 + self.click_element('button:has-text("🔄 重置")')
106 +
107 + # 等待重置完成
108 + self.page.wait_for_load_state('networkidle')
109 +
110 + # 验证搜索条件已清空
111 + delivery_no_input = self.page.query_selector('input[placeholder="请输入出库单编号"]')
112 + assert delivery_no_input.input_value() == '', "出库单编号输入框应该已清空"
113 +
114 + dealer_code_input = self.page.query_selector('input[placeholder="请输入经销商编码"]')
115 + assert dealer_code_input.input_value() == '', "经销商编码输入框应该已清空"
116 +
117 + # 截图验证重置结果
118 + self.take_screenshot('delivery_reset_search')
119 +
120 + print("重置搜索测试通过")
121 +
122 +
123 + def test_delivery_table_operations(self):
124 + """测试表格操作功能"""
125 +
126 + # 等待表格加载
127 + self.wait_for_element('.data-table')
128 +
129 + # 测试全选功能
130 + select_all_checkbox = self.page.query_selector('thead input[type="checkbox"]')
131 + if select_all_checkbox:
132 + select_all_checkbox.click()
133 + self.page.wait_for_timeout(500) # 等待选择完成
134 +
135 + # 截图验证全选结果
136 + self.take_screenshot('delivery_table_select_all')
137 +
138 + # 取消全选
139 + select_all_checkbox.click()
140 + self.page.wait_for_timeout(500)
141 +
142 + # 测试单行选择
143 + first_row_checkbox = self.page.query_selector('tbody tr:first-child input[type="checkbox"]')
144 + if first_row_checkbox:
145 + first_row_checkbox.click()
146 + self.page.wait_for_timeout(500)
147 +
148 + # 截图验证表格操作结果
149 + self.take_screenshot('delivery_table_operations')
150 +
151 + print("表格操作测试通过")
152 +
153 + def test_add_delivery(self):
154 + """测试新增出库功能"""
155 +
156 + # 等待操作按钮区域加载
157 + self.wait_for_element('.action-section')
158 +
159 + # 点击新增按钮
160 + if self.is_element_visible('button:has-text("✨ 新增")'):
161 + print("找到新增按钮,开始点击...")
162 + # 确保页面稳定后再点击
163 + self.page.wait_for_load_state('networkidle')
164 + self.click_element('button:has-text("✨ 新增")')
165 + self.page.wait_for_timeout(3000) # 等待弹窗打开
166 +
167 + # 检查是否打开了新增出库的弹窗
168 + if self.is_element_visible('.dialog-overlay'):
169 + print("✅ 新增出库弹窗已打开")
170 + # 等待弹窗完全加载
171 + self.page.wait_for_timeout(1000)
172 + self.take_screenshot('add_delivery_dialog_opened')
173 +
174 + # 1. 填写出库单编号 - 使用最精确的选择器,只在弹窗内操作
175 + delivery_no = 'TEST-OUT-0001'
176 + delivery_no_selectors = [
177 + '.dialog-content input.form-input[placeholder="请输入出库单编号"]', # 最精确:弹窗内的表单输入框
178 + '.dialog-overlay input.form-input[placeholder="请输入出库单编号"]', # 弹窗覆盖层内的表单输入框
179 + '.form-section input.form-input[placeholder="请输入出库单编号"]', # 表单区域内的输入框
180 + 'input.form-input[placeholder="请输入出库单编号"]:not(.search-input)' # 排除搜索框的表单输入框
181 + ]
182 +
183 + delivery_no_filled = False
184 + for selector in delivery_no_selectors:
185 + if self.is_element_visible(selector):
186 + print(f"找到出库单编号输入框: {selector}")
187 + try:
188 + # 先清空输入框
189 + self.page.fill(selector, '')
190 + self.page.wait_for_timeout(200)
191 +
192 + # 填写出库单编号
193 + self.page.fill(selector, delivery_no)
194 + self.page.wait_for_timeout(300)
195 +
196 + # 使用JavaScript直接设置值并触发Vue更新
197 + self.page.evaluate(f"""
198 + const input = document.querySelector('{selector}');
199 + if (input) {{
200 + input.value = '{delivery_no}';
201 + input.dispatchEvent(new Event('input', {{ bubbles: true }}));
202 + input.dispatchEvent(new Event('change', {{ bubbles: true }}));
203 + input.dispatchEvent(new Event('blur', {{ bubbles: true }}));
204 + }}
205 + """)
206 + self.page.wait_for_timeout(500)
207 +
208 + # 验证是否填写成功
209 + actual_value = self.page.input_value(selector)
210 + if actual_value == delivery_no:
211 + print(f"✅ 1. 填写出库单编号: {delivery_no}")
212 + delivery_no_filled = True
213 + break
214 + except Exception as e:
215 + print(f"填写出库单编号时出错: {e}")
216 +
217 + if not delivery_no_filled:
218 + print("❌ 未找到出库单编号输入框或填写失败")
219 +
220 + # 2. 选择经销商名称 - 只在弹窗内操作
221 + dealer_selectors = [
222 + '.dialog-content select.form-select',
223 + '.dialog-overlay select.form-select',
224 + '.form-section select.form-select',
225 + 'select.form-select',
226 + '.dialog-content select',
227 + '.dialog-overlay select',
228 + '.form-section select',
229 + 'select'
230 + ]
231 +
232 + dealer_selected = False
233 + for selector in dealer_selectors:
234 + dealer_selects = self.page.locator(selector)
235 + if dealer_selects.count() > 0:
236 + # 尝试每个select元素
237 + for i in range(dealer_selects.count()):
238 + dealer_select = dealer_selects.nth(i)
239 + if dealer_select.is_visible():
240 + options = dealer_select.locator('option')
241 + if options.count() > 1: # 除了"请选择"选项
242 + try:
243 + dealer_select.select_option(index=1)
244 + print(f"✅ 2. 选择经销商名称(默认第一个)- 使用选择器: {selector}[{i}]")
245 + dealer_selected = True
246 + self.page.wait_for_timeout(500)
247 + break
248 + except Exception as e:
249 + print(f"选择经销商时出错: {e}")
250 + continue
251 + if dealer_selected:
252 + break
253 +
254 + if not dealer_selected:
255 + print("❌ 未找到经销商选择框或无法选择")
256 + # 尝试查找所有select元素进行调试
257 + all_selects = self.page.locator('select')
258 + print(f"页面中找到 {all_selects.count()} 个select元素")
259 + for i in range(all_selects.count()):
260 + select = all_selects.nth(i)
261 + if select.is_visible():
262 + options = select.locator('option')
263 + print(f"Select {i}: {options.count()} 个选项")
264 + for j in range(min(options.count(), 3)): # 只显示前3个选项
265 + option_text = options.nth(j).text_content()
266 + print(f" 选项 {j}: {option_text}")
267 +
268 + # 3. 填写关联订单编号 - 只在弹窗内操作
269 + order_no = 'TEST-ORDER-0001'
270 + order_no_selectors = [
271 + '.dialog-content input[placeholder*="关联订单"]',
272 + '.form-section input[placeholder*="关联订单"]',
273 + 'input[placeholder*="关联订单"]',
274 + 'input[name*="order"]',
275 + 'input[name*="订单"]'
276 + ]
277 +
278 + order_no_filled = False
279 + for selector in order_no_selectors:
280 + if self.is_element_visible(selector):
281 + try:
282 + # 先清空输入框
283 + self.page.fill(selector, '')
284 + self.page.wait_for_timeout(200)
285 +
286 + # 填写关联订单编号
287 + self.page.fill(selector, order_no)
288 + self.page.wait_for_timeout(300)
289 +
290 + # 使用JavaScript直接设置值并触发Vue更新
291 + self.page.evaluate(f"""
292 + const input = document.querySelector('{selector}');
293 + if (input) {{
294 + input.value = '{order_no}';
295 + input.dispatchEvent(new Event('input', {{ bubbles: true }}));
296 + input.dispatchEvent(new Event('change', {{ bubbles: true }}));
297 + input.dispatchEvent(new Event('blur', {{ bubbles: true }}));
298 + }}
299 + """)
300 + self.page.wait_for_timeout(500)
301 +
302 + # 验证是否填写成功
303 + actual_value = self.page.input_value(selector)
304 + if actual_value == order_no:
305 + print(f"✅ 3. 填写关联订单编号: {order_no}")
306 + order_no_filled = True
307 + break
308 + except Exception as e:
309 + print(f"填写关联订单编号时出错: {e}")
310 +
311 + if not order_no_filled:
312 + print("❌ 未找到关联订单编号输入框或填写失败")
313 +
314 + # 4. 填写出库日期 - 只在弹窗内操作
315 + date_selectors = [
316 + '.dialog-content input[type="datetime-local"]',
317 + '.form-section input[type="datetime-local"]',
318 + 'input[type="datetime-local"]',
319 + '.dialog-content input[type="date"]',
320 + '.form-section input[type="date"]',
321 + 'input[type="date"]'
322 + ]
323 +
324 + date_filled = False
325 + for selector in date_selectors:
326 + if self.is_element_visible(selector):
327 + # 使用datetime-local格式:YYYY-MM-DDTHH:MM
328 + now = datetime.now()
329 + date_time_str = now.strftime('%Y-%m-%dT%H:%M')
330 + self.page.fill(selector, date_time_str)
331 + print(f"✅ 4. 填写出库日期: {date_time_str}")
332 + date_filled = True
333 + self.page.wait_for_timeout(500)
334 + break
335 +
336 + if not date_filled:
337 + print("❌ 未找到出库日期输入框")
338 +
339 + self.take_screenshot('add_delivery_form_filled')
340 +
341 + # 5. 添加出库明细
342 + if self.is_element_visible('button:has-text("+ 添加明细")'):
343 + self.click_element('button:has-text("+ 添加明细")')
344 + self.page.wait_for_timeout(2000) # 增加等待时间
345 + print("✅ 5. 添加出库明细")
346 +
347 + # 等待明细区域完全加载
348 + self.page.wait_for_load_state('networkidle')
349 + self.page.wait_for_timeout(1000)
350 +
351 + # 5.1 选择产品名称 - 只在弹窗内操作
352 + product_selectors = [
353 + '.dialog-content .item-row select', # 明细行中的select
354 + '.dialog-content .detail-row select', # 明细行中的select
355 + '.dialog-content .form-row select', # 表单行中的select
356 + '.dialog-content tbody select', # 表格体中的select
357 + '.dialog-content select:not(:first-child)', # 除了第一个select
358 + 'select.form-select',
359 + 'select[name*="product"]',
360 + 'select[placeholder*="产品"]',
361 + '.dialog-content select',
362 + '.dialog-overlay select',
363 + '.form-section select',
364 + 'select'
365 + ]
366 +
367 + product_selected = False
368 + for selector in product_selectors:
369 + product_selects = self.page.locator(selector)
370 + if product_selects.count() > 0:
371 + # 尝试每个select元素
372 + for i in range(product_selects.count()):
373 + product_select = product_selects.nth(i)
374 + if product_select.is_visible():
375 + options = product_select.locator('option')
376 + if options.count() > 1: # 除了"请选择"选项
377 + try:
378 + # 检查选项文本,找到产品相关的select
379 + first_option = options.nth(0).text_content()
380 + second_option = options.nth(1).text_content() if options.count() > 1 else ""
381 +
382 + # 更精确的判断:排除经销商相关的select
383 + if ('产品' in first_option or '请选择' in first_option) and '经销商' not in first_option:
384 + product_select.select_option(index=1)
385 + print(f"✅ 5.1 选择产品名称(第一个产品)- 使用选择器: {selector}[{i}]")
386 + print(f" 选择的产品: {second_option}")
387 + product_selected = True
388 + self.page.wait_for_timeout(500)
389 + break
390 + except Exception as e:
391 + print(f"选择产品时出错: {e}")
392 + continue
393 + if product_selected:
394 + break
395 +
396 + if not product_selected:
397 + print("❌ 未找到产品选择框或无法选择")
398 + # 尝试查找所有select元素进行调试
399 + all_selects = self.page.locator('select')
400 + print(f"添加明细后页面中找到 {all_selects.count()} 个select元素")
401 + for i in range(all_selects.count()):
402 + select = all_selects.nth(i)
403 + if select.is_visible():
404 + options = select.locator('option')
405 + print(f"Select {i}: {options.count()} 个选项")
406 + for j in range(min(options.count(), 3)): # 只显示前3个选项
407 + option_text = options.nth(j).text_content()
408 + print(f" 选项 {j}: {option_text}")
409 +
410 + # 检查这个select是否已经被使用过(经销商选择)
411 + try:
412 + current_value = select.input_value()
413 + if current_value:
414 + print(f" Select {i} 当前值: {current_value} (可能已被使用)")
415 + except:
416 + pass
417 +
418 + # 5.2 填写出库数量 - 只在弹窗内操作
419 + quantity_selectors = [
420 + '.dialog-content input[placeholder*="出库数量"]',
421 + '.form-section input[placeholder*="出库数量"]',
422 + 'input[placeholder*="出库数量"]',
423 + '.dialog-content input[placeholder*="数量"]',
424 + '.form-section input[placeholder*="数量"]',
425 + 'input[placeholder*="数量"]'
426 + ]
427 +
428 + quantity_filled = False
429 + for selector in quantity_selectors:
430 + if self.is_element_visible(selector):
431 + self.page.fill(selector, '10')
432 + print("✅ 5.2 填写出库数量: 10")
433 + quantity_filled = True
434 + self.page.wait_for_timeout(500)
435 + break
436 +
437 + if not quantity_filled:
438 + print("❌ 未找到出库数量输入框")
439 +
440 + # 5.3 填写出库单价 - 只在弹窗内操作
441 + price_selectors = [
442 + '.dialog-content input[placeholder*="出库单价"]',
443 + '.form-section input[placeholder*="出库单价"]',
444 + 'input[placeholder*="出库单价"]',
445 + '.dialog-content input[placeholder*="单价"]',
446 + '.form-section input[placeholder*="单价"]',
447 + 'input[placeholder*="单价"]'
448 + ]
449 +
450 + price_filled = False
451 + for selector in price_selectors:
452 + if self.is_element_visible(selector):
453 + self.page.fill(selector, '100')
454 + print("✅ 5.3 填写出库单价: 100")
455 + price_filled = True
456 + self.page.wait_for_timeout(500)
457 + break
458 +
459 + if not price_filled:
460 + print("❌ 未找到出库单价输入框")
461 +
462 + self.take_screenshot('add_delivery_with_items')
463 +
464 + # 6. 点击确认保存出库
465 + confirm_button_selectors = [
466 + 'button:has-text("确定")',
467 + 'button:has-text("保存")',
468 + 'button:has-text("提交")',
469 + 'button[type="submit"]',
470 + '.submit-btn'
471 + ]
472 +
473 + delivery_saved = False
474 + for selector in confirm_button_selectors:
475 + if self.is_element_visible(selector):
476 + print(f"💾 6. 开始保存出库,点击按钮: {selector}")
477 + self.click_element(selector)
478 +
479 + # 等待保存处理
480 + self.page.wait_for_timeout(3000)
481 +
482 + # 检查是否有成功提示
483 + page_text = self.page.text_content('body')
484 + if '新增出库成功' in page_text or '保存成功' in page_text:
485 + print("✅ 页面显示:新增出库成功")
486 + has_success = True
487 + else:
488 + has_success = False
489 +
490 + if has_success:
491 + print("✅ 保存成功,等待弹窗关闭...")
492 + # 成功提示出现后,等待弹窗关闭
493 + self.page.wait_for_timeout(3000)
494 + if not self.is_element_visible('.dialog-overlay'):
495 + print("✅ 出库保存成功!弹窗已关闭")
496 + self.take_screenshot('delivery_saved_successfully')
497 + delivery_saved = True
498 + else:
499 + print("⚠️ 成功提示出现但弹窗未关闭,可能延迟关闭")
500 + self.take_screenshot('delivery_save_success_but_dialog_open')
501 + else:
502 + # 没有明确的成功提示,检查弹窗状态
503 + self.page.wait_for_timeout(3000)
504 +
505 + # 检查保存结果
506 + if not self.is_element_visible('.dialog-overlay'):
507 + print("✅ 出库保存成功!弹窗已关闭")
508 + self.take_screenshot('delivery_saved_successfully')
509 + delivery_saved = True
510 + else:
511 + print("⚠️ 弹窗仍然存在,可能保存失败或需要更多时间")
512 + # 再等待一下
513 + self.page.wait_for_timeout(5000)
514 + if not self.is_element_visible('.dialog-overlay'):
515 + print("✅ 出库保存成功!(延迟确认)")
516 + self.take_screenshot('delivery_saved_successfully_delayed')
517 + delivery_saved = True
518 + else:
519 + print("❌ 出库保存失败,弹窗仍然存在")
520 + self.take_screenshot('delivery_save_failed')
521 +
522 + break
523 +
524 + if not delivery_saved:
525 + print("❌ 未找到确认按钮或保存失败")
526 + # 如果保存失败,尝试关闭弹窗
527 + cancel_button_selectors = [
528 + 'button:has-text("取消")',
529 + 'button:has-text("关闭")',
530 + '.el-dialog__close'
531 + ]
532 +
533 + for selector in cancel_button_selectors:
534 + if self.is_element_visible(selector):
535 + self.click_element(selector)
536 + self.page.wait_for_timeout(1000)
537 + print(f"✅ 点击取消按钮关闭弹窗: {selector}")
538 + break
539 +
540 + else:
541 + print("❌ 新增出库弹窗未打开")
542 + self.take_screenshot('add_delivery_dialog_not_opened')
543 + else:
544 + print("❌ 未找到新增按钮")
545 + self.take_screenshot('add_button_not_found')
546 +
547 + print("新增出库功能测试通过")
548 +
549 + def test_edit_delivery(self):
550 + """测试编辑出库功能"""
551 +
552 + # 等待数据表格加载
553 + self.wait_for_element('.data-table')
554 +
555 + # 查找 TEST-OUT-0001 出库单的编辑按钮
556 + # 先尝试通过出库单编号找到对应的行
557 + delivery_row = None
558 + edit_button = None
559 +
560 + # 方法1:通过表格行查找
561 + table_rows = self.page.locator('.data-table tbody tr')
562 + for i in range(table_rows.count()):
563 + row = table_rows.nth(i)
564 + row_text = row.text_content()
565 + if 'TEST-OUT-0001' in row_text:
566 + print(f"✅ 找到出库单 TEST-OUT-0001 在第 {i+1} 行")
567 + delivery_row = row
568 + # 在该行中查找编辑按钮
569 + edit_button = row.locator('button:has-text("编辑")').first
570 + if edit_button.count() > 0:
571 + break
572 + # 如果没有找到编辑按钮,尝试其他可能的按钮文本
573 + edit_button = row.locator('button:has-text("✏️")').first
574 + if edit_button.count() > 0:
575 + break
576 + edit_button = row.locator('button[title*="编辑"]').first
577 + if edit_button.count() > 0:
578 + break
579 +
580 + if edit_button and edit_button.count() > 0:
581 + print("找到编辑按钮,开始点击...")
582 + # 确保页面稳定
583 + self.page.wait_for_load_state('networkidle')
584 + edit_button.click()
585 + self.page.wait_for_timeout(3000) # 等待编辑弹窗打开
586 +
587 + # 检查是否打开了编辑出库的弹窗
588 + if self.is_element_visible('.dialog-overlay'):
589 + print("✅ 编辑出库弹窗已打开")
590 + self.take_screenshot('edit_delivery_dialog_opened')
591 +
592 + # 查找仓库编码输入框并修改
593 + warehouse_code_selectors = [
594 + '.dialog-content input[placeholder*="仓库编码"]',
595 + '.form-section input[placeholder*="仓库编码"]',
596 + 'input[placeholder*="仓库编码"]',
597 + 'input[name*="warehouse"]',
598 + 'input[name*="仓库"]'
599 + ]
600 +
601 + warehouse_code_updated = False
602 + for selector in warehouse_code_selectors:
603 + if self.is_element_visible(selector):
604 + # 获取当前仓库编码
605 + current_warehouse_code = self.page.input_value(selector)
606 + print(f"当前仓库编码: {current_warehouse_code}")
607 +
608 + # 设置新的仓库编码
609 + new_warehouse_code = 'WH001'
610 +
611 + try:
612 + # 清空并填写新的仓库编码
613 + self.page.fill(selector, '')
614 + self.page.wait_for_timeout(200)
615 + self.page.fill(selector, new_warehouse_code)
616 + self.page.wait_for_timeout(300)
617 +
618 + # 使用JavaScript直接设置值并触发Vue更新
619 + self.page.evaluate(f"""
620 + const input = document.querySelector('{selector}');
621 + if (input) {{
622 + input.value = '{new_warehouse_code}';
623 + input.dispatchEvent(new Event('input', {{ bubbles: true }}));
624 + input.dispatchEvent(new Event('change', {{ bubbles: true }}));
625 + input.dispatchEvent(new Event('blur', {{ bubbles: true }}));
626 + }}
627 + """)
628 + self.page.wait_for_timeout(500)
629 +
630 + # 验证是否修改成功
631 + actual_value = self.page.input_value(selector)
632 + if actual_value == new_warehouse_code:
633 + print(f"✅ 仓库编码已更新: {current_warehouse_code} → {new_warehouse_code}")
634 + warehouse_code_updated = True
635 + break
636 + except Exception as e:
637 + print(f"修改仓库编码时出错: {e}")
638 +
639 + if not warehouse_code_updated:
640 + print("❌ 未找到仓库编码输入框或修改失败")
641 +
642 + self.take_screenshot('edit_delivery_warehouse_updated')
643 +
644 + # 点击保存按钮
645 + save_button_selectors = [
646 + 'button:has-text("确定")',
647 + 'button:has-text("保存")',
648 + 'button:has-text("更新")',
649 + 'button:has-text("提交")',
650 + 'button[type="submit"]',
651 + '.submit-btn'
652 + ]
653 +
654 + delivery_saved = False
655 + for selector in save_button_selectors:
656 + if self.is_element_visible(selector):
657 + print(f"💾 开始保存编辑,点击按钮: {selector}")
658 + self.click_element(selector)
659 +
660 + # 等待保存处理
661 + self.page.wait_for_timeout(3000)
662 +
663 + # 检查是否有成功提示
664 + page_text = self.page.text_content('body')
665 + if '更新成功' in page_text or '保存成功' in page_text or '修改成功' in page_text:
666 + print("✅ 页面显示:出库更新成功")
667 + has_success = True
668 + else:
669 + has_success = False
670 +
671 + if has_success:
672 + print("✅ 保存成功,等待弹窗关闭...")
673 + # 成功提示出现后,等待弹窗关闭
674 + self.page.wait_for_timeout(3000)
675 + if not self.is_element_visible('.dialog-overlay'):
676 + print("✅ 出库编辑保存成功!弹窗已关闭")
677 + self.take_screenshot('delivery_edit_saved_successfully')
678 + delivery_saved = True
679 + else:
680 + print("⚠️ 成功提示出现但弹窗未关闭,可能延迟关闭")
681 + self.take_screenshot('delivery_edit_save_success_but_dialog_open')
682 + else:
683 + # 没有明确的成功提示,检查弹窗状态
684 + self.page.wait_for_timeout(3000)
685 +
686 + # 检查保存结果
687 + if not self.is_element_visible('.dialog-overlay'):
688 + print("✅ 出库编辑保存成功!弹窗已关闭")
689 + self.take_screenshot('delivery_edit_saved_successfully')
690 + delivery_saved = True
691 + else:
692 + print("⚠️ 弹窗仍然存在,可能保存失败或需要更多时间")
693 + # 再等待一下
694 + self.page.wait_for_timeout(5000)
695 + if not self.is_element_visible('.dialog-overlay'):
696 + print("✅ 出库编辑保存成功!(延迟确认)")
697 + self.take_screenshot('delivery_edit_saved_successfully_delayed')
698 + delivery_saved = True
699 + else:
700 + print("❌ 出库编辑保存失败,弹窗仍然存在")
701 + self.take_screenshot('delivery_edit_save_failed')
702 +
703 + break
704 +
705 + if not delivery_saved:
706 + print("❌ 未找到保存按钮或保存失败")
707 + # 如果保存失败,尝试关闭弹窗
708 + cancel_button_selectors = [
709 + 'button:has-text("取消")',
710 + 'button:has-text("关闭")',
711 + '.el-dialog__close'
712 + ]
713 +
714 + for selector in cancel_button_selectors:
715 + if self.is_element_visible(selector):
716 + self.click_element(selector)
717 + self.page.wait_for_timeout(1000)
718 + print(f"✅ 点击取消按钮关闭弹窗: {selector}")
719 + break
720 +
721 + else:
722 + print("❌ 编辑出库弹窗未打开")
723 + self.take_screenshot('edit_delivery_dialog_not_opened')
724 + else:
725 + print("❌ 未找到 TEST-OUT-0001 出库单或编辑按钮")
726 + self.take_screenshot('edit_button_not_found')
727 +
728 + print("编辑出库功能测试通过")
729 +
730 + def test_view_delivery(self):
731 + """测试查看出库功能:打开详情弹窗并在2秒后关闭"""
732 +
733 + # 确保表格加载完成
734 + self.wait_for_element('.data-table')
735 +
736 + # 查找包含 TEST-OUT-0001 的行
737 + table_rows = self.page.locator('.data-table tbody tr')
738 + view_button = None
739 + for i in range(table_rows.count()):
740 + row = table_rows.nth(i)
741 + row_text = row.text_content()
742 + if 'TEST-OUT-0001' in row_text:
743 + print(f"✅ 找到出库单 TEST-OUT-0001 在第 {i+1} 行(用于查看)")
744 + # 尝试不同的查看按钮选择器
745 + candidates = [
746 + 'button:has-text("查看")',
747 + 'button:has-text("详情")',
748 + 'button[title*="查看"]',
749 + 'a:has-text("查看")',
750 + 'a[title*="查看"]',
751 + 'button:has-text("👁")',
752 + ]
753 + for sel in candidates:
754 + btn = row.locator(sel).first
755 + if btn and btn.count() > 0:
756 + view_button = btn
757 + break
758 + break
759 +
760 + if view_button and view_button.count() > 0:
761 + print("找到查看按钮,开始点击...")
762 + self.page.wait_for_load_state('networkidle')
763 + view_button.click()
764 + self.page.wait_for_timeout(1500)
765 +
766 + # 确认弹窗已打开
767 + if self.is_element_visible('.dialog-overlay'):
768 + print("✅ 查看出库弹窗已打开")
769 + self.take_screenshot('view_delivery_dialog_opened')
770 +
771 + # 等待2秒以模拟查看
772 + self.page.wait_for_timeout(2000)
773 +
774 + # 关闭弹窗(尝试多种关闭方式)
775 + close_selectors = [
776 + 'button:has-text("关闭")',
777 + 'button:has-text("取消")',
778 + '.el-dialog__close',
779 + '.dialog-overlay .close-btn',
780 + ]
781 + closed = False
782 + for sel in close_selectors:
783 + if self.is_element_visible(sel):
784 + self.click_element(sel)
785 + self.page.wait_for_timeout(1000)
786 + if not self.is_element_visible('.dialog-overlay'):
787 + closed = True
788 + break
789 + if not closed:
790 + print("⚠️ 未找到关闭按钮,尝试按下ESC")
791 + self.page.keyboard.press('Escape')
792 + self.page.wait_for_timeout(1000)
793 +
794 + if not self.is_element_visible('.dialog-overlay'):
795 + print("✅ 查看弹窗已关闭")
796 + self.take_screenshot('view_delivery_dialog_closed')
797 + else:
798 + print("❌ 查看弹窗关闭失败")
799 + self.take_screenshot('view_delivery_dialog_close_failed')
800 + else:
801 + print("❌ 查看出库弹窗未打开")
802 + self.take_screenshot('view_delivery_dialog_not_opened')
803 + else:
804 + print("❌ 未找到 TEST-OUT-0001 对应的查看按钮")
805 + self.take_screenshot('view_button_not_found')
806 +
807 + print("查看出库功能测试通过")
808 +
809 +
810 +def run_delivery_management_tests():
811 + """运行出库管理测试"""
812 + test_results = []
813 +
814 + # 创建测试实例
815 + test = DeliveryManagementTest(headless=False, slow_mo=500) # 非无头模式,慢速执行
816 +
817 + # 先设置浏览器并登录一次
818 + try:
819 + test.setup_browser()
820 + # 手动登录流程
821 + print("开始登录...")
822 + test.navigate_to(test.base_url)
823 + test.fill_input('input[placeholder="请输入用户名"]', 'admin')
824 + test.fill_input('input[placeholder="请输入密码"]', 'password')
825 + test.click_element('button:has-text("登录")')
826 + test.page.wait_for_timeout(5000)
827 +
828 + # 检查登录是否成功
829 + current_url = test.page.url
830 + if 'dashboard' in current_url:
831 + print("✅ 登录成功")
832 + login_success = True
833 + else:
834 + print(f"❌ 登录失败,当前URL: {current_url}")
835 + login_success = False
836 +
837 + if not login_success:
838 + print("❌ 登录失败,无法运行出库管理测试")
839 + return []
840 +
841 + # 导航到出库管理页面
842 + test.navigate_to(f'{test.base_url}/main/delivery')
843 + test.wait_for_element('.delivery-container', timeout=15000)
844 + print("✅ 成功进入出库管理页面")
845 +
846 + # 定义测试用例
847 + test_cases = [
848 + ("按出库单编号搜索测试", test.test_delivery_search_by_delivery_no),
849 + ("按经销商搜索测试", test.test_delivery_search_by_dealer),
850 + ("出库状态筛选测试", test.test_delivery_status_filter),
851 + ("重置搜索测试", test.test_delivery_reset_search),
852 + ("表格操作测试", test.test_delivery_table_operations),
853 + ("新增出库功能测试", test.test_add_delivery),
854 + ("编辑出库功能测试", test.test_edit_delivery),
855 + ("查看出库功能测试", test.test_view_delivery),
856 + ]
857 +
858 + # 运行所有测试(不重新创建浏览器)
859 + results = []
860 + for test_name, test_func in test_cases:
861 + print(f"\n{'='*50}")
862 + print(f"开始测试: {test_name}")
863 + print(f"{'='*50}")
864 +
865 + start_time = datetime.now()
866 + result = {
867 + 'test_name': test_name,
868 + 'start_time': start_time.isoformat(),
869 + 'success': False,
870 + 'error': None,
871 + 'screenshots': [],
872 + 'duration': 0
873 + }
874 +
875 + try:
876 + test_func()
877 + result['success'] = True
878 + print(f"✅ 测试通过: {test_name}")
879 + except Exception as e:
880 + result['error'] = str(e)
881 + print(f"❌ 测试失败: {test_name}")
882 + print(f"错误信息: {e}")
883 + screenshot_path = test.take_screenshot(f'{test_name}_error')
884 + result['screenshots'].append(screenshot_path)
885 + finally:
886 + end_time = datetime.now()
887 + result['end_time'] = end_time.isoformat()
888 + result['duration'] = (end_time - start_time).total_seconds()
889 + print(f"测试耗时: {result['duration']:.2f}秒")
890 +
891 + results.append(result)
892 +
893 + return results
894 +
895 + finally:
896 + test.teardown_browser()
897 +
898 + # 打印测试总结
899 + print(f"\n{'='*60}")
900 + print("出库管理测试总结")
901 + print(f"{'='*60}")
902 +
903 + passed = sum(1 for r in results if r['success'])
904 + total = len(results)
905 +
906 + for result in results:
907 + status = "✅ 通过" if result['success'] else "❌ 失败"
908 + print(f"{result['test_name']}: {status} ({result['duration']:.2f}s)")
909 + if result['error']:
910 + print(f" 错误: {result['error']}")
911 +
912 + print(f"\n总计: {passed}/{total} 个测试通过")
913 +
914 +
915 +if __name__ == '__main__':
916 + run_delivery_management_tests()
...@@ -9,7 +9,9 @@ from datetime import datetime ...@@ -9,7 +9,9 @@ from datetime import datetime
9 from typing import List, Dict, Any 9 from typing import List, Dict, Any
10 10
11 from test_login import run_login_tests 11 from test_login import run_login_tests
12 -from test_order_management import run_order_management_tests 12 +from test_order_management import run_order_management_tests, OrderManagementTest
13 +from test_delivery_management import run_delivery_management_tests, DeliveryManagementTest
14 +from base_test import BaseBrowserTest
13 15
14 16
15 class SimpleTestRunner: 17 class SimpleTestRunner:
...@@ -282,6 +284,150 @@ class SimpleTestRunner: ...@@ -282,6 +284,150 @@ class SimpleTestRunner:
282 284
283 return report_file 285 return report_file
284 286
287 + def run_shared_browser_tests(self) -> List[Dict[str, Any]]:
288 + """运行共享浏览器实例的测试(订单管理 + 出库管理)"""
289 + print("🚀 开始运行共享浏览器测试...")
290 + print("=" * 60)
291 +
292 + all_results = []
293 + shared_browser = None
294 +
295 + try:
296 + # 创建共享的浏览器实例
297 + shared_browser = BaseBrowserTest(headless=False, slow_mo=500)
298 + shared_browser.setup_browser()
299 +
300 + # 手动登录一次
301 + print("开始登录...")
302 + shared_browser.navigate_to(shared_browser.base_url)
303 + shared_browser.fill_input('input[placeholder="请输入用户名"]', 'admin')
304 + shared_browser.fill_input('input[placeholder="请输入密码"]', 'password')
305 + shared_browser.click_element('button:has-text("登录")')
306 + shared_browser.page.wait_for_timeout(5000)
307 +
308 + # 检查登录是否成功
309 + current_url = shared_browser.page.url
310 + if 'dashboard' in current_url:
311 + print("✅ 登录成功")
312 + login_success = True
313 + else:
314 + print(f"❌ 登录失败,当前URL: {current_url}")
315 + login_success = False
316 +
317 + if not login_success:
318 + print("❌ 登录失败,无法运行共享浏览器测试")
319 + return []
320 +
321 + # 1. 运行订单管理测试(使用共享浏览器)
322 + print("\n📋 运行订单管理测试(共享浏览器)...")
323 + try:
324 + order_test = OrderManagementTest(headless=False, slow_mo=500)
325 + # 直接使用已创建的浏览器实例
326 + order_test.browser = shared_browser.browser
327 + order_test.page = shared_browser.page
328 + order_test.context = shared_browser.context
329 +
330 + # 导航到订单管理页面
331 + order_test.navigate_to(f'{order_test.base_url}/main/order')
332 + order_test.wait_for_element('.order-container', timeout=15000)
333 + print("✅ 成功进入订单管理页面")
334 +
335 + # 运行订单管理测试用例
336 + order_results = self._run_test_cases(order_test, [
337 + ("订单列表显示测试", order_test.test_order_list_display),
338 + ("按订单编号搜索测试", order_test.test_order_search_by_order_no),
339 + ("按经销商搜索测试", order_test.test_order_search_by_dealer),
340 + ("订单状态筛选测试", order_test.test_order_status_filter),
341 + ("重置搜索测试", order_test.test_reset_search),
342 + ("分页功能测试", order_test.test_pagination),
343 + ("表格操作测试", order_test.test_table_operations),
344 + ("新增订单功能测试", order_test.test_add_order),
345 + ("编辑订单功能测试", order_test.test_edit_order),
346 + ("查看订单功能测试", order_test.test_view_order),
347 + ])
348 +
349 + all_results.extend(order_results)
350 + print(f"✅ 订单管理测试完成,共 {len(order_results)} 个测试")
351 + except Exception as e:
352 + print(f"❌ 订单管理测试执行失败: {e}")
353 +
354 + # 2. 运行出库管理测试(使用同一个浏览器实例)
355 + print("\n📋 运行出库管理测试(共享浏览器)...")
356 + try:
357 + delivery_test = DeliveryManagementTest(headless=False, slow_mo=500)
358 + # 直接使用已创建的浏览器实例
359 + delivery_test.browser = shared_browser.browser
360 + delivery_test.page = shared_browser.page
361 + delivery_test.context = shared_browser.context
362 +
363 + # 导航到出库管理页面
364 + delivery_test.navigate_to(f'{delivery_test.base_url}/main/delivery')
365 + delivery_test.wait_for_element('.delivery-container', timeout=15000)
366 + print("✅ 成功进入出库管理页面")
367 +
368 + # 运行出库管理测试用例
369 + delivery_results = self._run_test_cases(delivery_test, [
370 + ("按出库单编号搜索测试", delivery_test.test_delivery_search_by_delivery_no),
371 + ("按经销商搜索测试", delivery_test.test_delivery_search_by_dealer),
372 + ("出库状态筛选测试", delivery_test.test_delivery_status_filter),
373 + ("重置搜索测试", delivery_test.test_delivery_reset_search),
374 + ("表格操作测试", delivery_test.test_delivery_table_operations),
375 + ("新增出库功能测试", delivery_test.test_add_delivery),
376 + ("编辑出库功能测试", delivery_test.test_edit_delivery),
377 + ("查看出库功能测试", delivery_test.test_view_delivery),
378 + ])
379 +
380 + all_results.extend(delivery_results)
381 + print(f"✅ 出库管理测试完成,共 {len(delivery_results)} 个测试")
382 + except Exception as e:
383 + print(f"❌ 出库管理测试执行失败: {e}")
384 +
385 + finally:
386 + # 最后关闭共享浏览器
387 + if shared_browser:
388 + shared_browser.teardown_browser()
389 + print("✅ 共享浏览器已关闭")
390 +
391 + return all_results
392 +
393 + def _run_test_cases(self, test_instance, test_cases):
394 + """运行测试用例的通用方法"""
395 + results = []
396 + for test_name, test_func in test_cases:
397 + print(f"\n{'='*50}")
398 + print(f"开始测试: {test_name}")
399 + print(f"{'='*50}")
400 +
401 + start_time = datetime.now()
402 + result = {
403 + 'test_name': test_name,
404 + 'start_time': start_time.isoformat(),
405 + 'success': False,
406 + 'error': None,
407 + 'screenshots': [],
408 + 'duration': 0
409 + }
410 +
411 + try:
412 + test_func()
413 + result['success'] = True
414 + print(f"✅ 测试通过: {test_name}")
415 + except Exception as e:
416 + result['error'] = str(e)
417 + print(f"❌ 测试失败: {test_name}")
418 + print(f"错误信息: {e}")
419 + screenshot_path = test_instance.take_screenshot(f'{test_name}_error')
420 + result['screenshots'].append(screenshot_path)
421 + finally:
422 + end_time = datetime.now()
423 + result['end_time'] = end_time.isoformat()
424 + result['duration'] = (end_time - start_time).total_seconds()
425 + print(f"测试耗时: {result['duration']:.2f}秒")
426 +
427 + results.append(result)
428 +
429 + return results
430 +
285 def run_all_tests(self) -> List[Dict[str, Any]]: 431 def run_all_tests(self) -> List[Dict[str, Any]]:
286 """运行所有测试 - 顺序执行""" 432 """运行所有测试 - 顺序执行"""
287 print("🚀 开始运行浏览器自动化测试...") 433 print("🚀 开始运行浏览器自动化测试...")
...@@ -298,14 +444,14 @@ class SimpleTestRunner: ...@@ -298,14 +444,14 @@ class SimpleTestRunner:
298 except Exception as e: 444 except Exception as e:
299 print(f"❌ 登录测试执行失败: {e}") 445 print(f"❌ 登录测试执行失败: {e}")
300 446
301 - # 2. 运行订单管理测试 447 + # 2. 运行共享浏览器测试(订单管理 + 出库管理)
302 - print("\n📋 运行订单管理测试...") 448 + print("\n📋 运行共享浏览器测试...")
303 try: 449 try:
304 - order_results = run_order_management_tests() 450 + shared_results = self.run_shared_browser_tests()
305 - all_results.extend(order_results) 451 + all_results.extend(shared_results)
306 - print(f"✅ 订单管理测试完成,共 {len(order_results)} 个测试") 452 + print(f"✅ 共享浏览器测试完成,共 {len(shared_results)} 个测试")
307 except Exception as e: 453 except Exception as e:
308 - print(f"❌ 订单管理测试执行失败: {e}") 454 + print(f"❌ 共享浏览器测试执行失败: {e}")
309 455
310 # 生成报告 456 # 生成报告
311 print("\n📊 生成测试报告...") 457 print("\n📊 生成测试报告...")
......