Showing
3 changed files
with
1088 additions
and
17 deletions
| ... | @@ -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 = { | ... | ... |
tests/browser/test_delivery_management.py
0 → 100644
| 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📊 生成测试报告...") | ... | ... |
-
Please register or login to post a comment