import unittest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
import time
import logging
import os
import sys
from datetime import datetime
def setup_logger():
"""配置日志记录器"""
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# 创建控制台处理器
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.INFO)
# 创建文件处理器
if not os.path.exists('logs'):
os.makedirs('logs')
file_handler = logging.FileHandler(f'logs/test_{datetime.now().strftime("%Y%m%d_%H%M%S")}.log', encoding='utf-8')
file_handler.setLevel(logging.INFO)
# 创建格式化器
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
file_handler.setFormatter(formatter)
# 添加处理器到日志记录器
logger.addHandler(console_handler)
logger.addHandler(file_handler)
return logger
# 配置日志
logger = setup_logger()
class TestWarehouseSystem(unittest.TestCase):
def setUp(self):
"""设置测试环境"""
logger.info("开始设置测试环境")
logger.info("初始化 WebDriver")
# 设置火狐浏览器路径
firefox_path = r"C:\Program Files\Mozilla Firefox\firefox.exe"
options = webdriver.FirefoxOptions()
options.binary_location = firefox_path
# 初始化火狐浏览器
self.driver = webdriver.Firefox(options=options)
self.driver.maximize_window()
self.wait = WebDriverWait(self.driver, 10)
# 登录系统
try:
# 访问登录页面
logger.info("访问登录页面")
self.driver.get('http://localhost:5000/login')
time.sleep(2) # 等待页面加载
# 登录
logger.info("开始登录流程")
username_input = self.wait.until(EC.presence_of_element_located((By.NAME, 'username')))
password_input = self.wait.until(EC.presence_of_element_located((By.NAME, 'password')))
username_input.send_keys('admin')
password_input.send_keys('admin')
logger.info("点击登录按钮")
login_button = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'button[type="submit"]')))
login_button.click()
# 等待登录成功
logger.info("等待登录成功")
time.sleep(2)
# 验证登录成功
try:
flash_message = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'alert-success')))
self.assertIn('登录成功', flash_message.text)
logger.info("登录成功")
except:
# 如果登录失败,记录错误信息
error_message = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'alert-danger')))
logger.error(f"登录失败: {error_message.text}")
raise Exception(f"登录失败: {error_message.text}")
except Exception as e:
logger.error(f"设置测试环境时出错: {str(e)}")
# 确保关闭浏览器
try:
self.driver.quit()
except:
pass
raise
def tearDown(self):
"""清理测试环境"""
pass # 不再需要清理
def test_warehouse_operations(self):
"""测试仓储管理系统的主要功能"""
logger.info("开始测试仓储管理系统的主要功能")
try:
# 批量添加商品
logger.info("开始批量添加商品")
self.driver.get('http://localhost:5000/batch_add_products')
time.sleep(1)
# 设置添加数量为5
count_input = self.wait.until(EC.presence_of_element_located((By.ID, 'count')))
count_input.clear()
count_input.send_keys('5')
logger.info("设置添加数量为5")
time.sleep(1) # 等待输入完成
# 使用JavaScript直接调用updateForm函数
self.driver.execute_script("updateForm();")
logger.info("手动触发表单更新")
time.sleep(2) # 等待表单更新
# 填写5个商品的信息
timestamp = int(time.time())
for i in range(5):
product_code = f'TEST{timestamp}_{i+1:03d}' # 生成唯一的商品编号
name = f'测试商品{i+1}'
description = f'这是测试商品{i+1}的描述'
price = f'{99.99 * (i + 1):.2f}' # 限制价格小数点后两位
quantity = str(100 * (i + 1))
try:
# 获取当前商品卡片
card = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, f'#productsContainer .card:nth-child({i+1})')))
# 滚动到卡片位置
self.driver.execute_script("arguments[0].scrollIntoView(true);", card)
time.sleep(0.5) # 等待滚动完成
# 使用JavaScript填写表单
self.driver.execute_script(f"document.getElementsByName('product_code_{i}')[0].value = '{product_code}';")
self.driver.execute_script(f"document.getElementsByName('name_{i}')[0].value = '{name}';")
self.driver.execute_script(f"document.getElementsByName('description_{i}')[0].value = '{description}';")
self.driver.execute_script(f"document.getElementsByName('price_{i}')[0].value = '{price}';")
self.driver.execute_script(f"document.getElementsByName('quantity_{i}')[0].value = '{quantity}';")
logger.info(f"填写商品 {i+1} 信息完成: 编号={product_code}, 名称={name}, 价格={price}")
time.sleep(1) # 等待填写完成
except Exception as e:
logger.error(f"填写商品 {i+1} 信息失败: {str(e)}")
raise
# 提交表单
logger.info("提交批量添加表单")
# 滚动到表单底部
self.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(1) # 等待滚动完成
# 找到提交按钮并确保它可见
submit_button = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'button[type="submit"]')))
self.driver.execute_script("arguments[0].scrollIntoView(true);", submit_button)
time.sleep(1) # 等待滚动完成
# 使用JavaScript点击按钮
self.driver.execute_script("arguments[0].click();", submit_button)
logger.info("已点击提交按钮")
time.sleep(5) # 增加等待时间
# 验证添加成功
try:
# 等待页面加载完成
self.wait.until(EC.presence_of_element_located((By.TAG_NAME, 'body')))
time.sleep(2) # 额外等待
# 检查是否有成功消息
try:
success_message = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'alert-success')))
self.assertIn('批量添加商品成功', success_message.text)
logger.info("批量添加商品成功")
except:
# 如果没有成功消息,检查是否已经跳转到商品列表页面
current_url = self.driver.current_url
if '/products' in current_url:
logger.info("已跳转到商品列表页面,认为添加成功")
else:
raise Exception("未找到成功消息且未跳转到商品列表页面")
time.sleep(3) # 等待查看结果
# 获取添加的商品编号
self.driver.get('http://localhost:5000/products')
time.sleep(3) # 增加等待时间
product_ids = []
product_rows = self.driver.find_elements(By.CSS_SELECTOR, 'table tbody tr')
for row in product_rows:
try:
product_id = row.find_element(By.CSS_SELECTOR, 'td:first-child').text
product_code = row.find_element(By.CSS_SELECTOR, 'td:nth-child(2)').text
product_name = row.find_element(By.CSS_SELECTOR, 'td:nth-child(3)').text
if product_code and product_name: # 只添加有效的商品
product_ids.append((product_id, product_code, product_name))
except:
continue
logger.info(f"添加的商品列表: {product_ids}")
except Exception as e:
logger.error(f"验证添加结果时出错: {str(e)}")
raise
# 修改第一个商品
logger.info("开始修改商品")
# 确保回到商品列表页面
self.driver.get('http://localhost:5000/products')
time.sleep(2) # 等待页面加载
# 找到第一个商品的编辑按钮
edit_button = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'table tbody tr:first-child a.btn-primary')))
# 滚动到按钮位置
self.driver.execute_script("arguments[0].scrollIntoView(true);", edit_button)
time.sleep(1) # 等待滚动完成
edit_button.click()
logger.info("点击编辑按钮")
time.sleep(2) # 等待编辑页面加载
# 修改商品信息
logger.info("修改商品信息")
name_input = self.wait.until(EC.presence_of_element_located((By.NAME, 'name')))
name_input.clear()
name_input.send_keys('修改后的商品名称')
time.sleep(1) # 等待输入完成
description_input = self.wait.until(EC.presence_of_element_located((By.NAME, 'description')))
description_input.clear()
description_input.send_keys('这是修改后的商品描述')
time.sleep(1) # 等待输入完成
# 修改库存数量
quantity_input = self.wait.until(EC.presence_of_element_located((By.NAME, 'quantity')))
current_quantity = int(quantity_input.get_attribute('value'))
new_quantity = current_quantity + 50 # 增加50个库存
quantity_input.clear()
quantity_input.send_keys(str(new_quantity))
time.sleep(1) # 等待输入完成
logger.info(f"将库存数量从 {current_quantity} 修改为 {new_quantity}")
# 提交修改
logger.info("提交修改表单")
submit_button = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'button[type="submit"]')))
# 滚动到按钮位置
self.driver.execute_script("arguments[0].scrollIntoView(true);", submit_button)
time.sleep(1) # 等待滚动完成
submit_button.click()
time.sleep(2) # 等待提交完成
# 验证商品更新成功
success_message = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'alert-success')))
self.assertIn('更新成功', success_message.text)
logger.info("商品更新成功")
time.sleep(2)
# 开始搜索商品
logger.info("开始搜索商品")
# 确保在商品列表页面
self.driver.get('http://localhost:5000/products')
time.sleep(2) # 等待页面加载
# 选择按名称搜索
search_type = self.wait.until(EC.presence_of_element_located((By.NAME, 'type')))
self.driver.execute_script("arguments[0].value = 'name';", search_type)
logger.info("选择按名称搜索")
time.sleep(1) # 等待选择完成
# 输入搜索关键词
search_input = self.wait.until(EC.presence_of_element_located((By.NAME, 'query')))
search_input.clear()
search_input.send_keys('修改后的')
logger.info("输入搜索关键词")
time.sleep(1) # 等待输入完成
# 点击搜索按钮
logger.info("点击搜索按钮")
search_button = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'button[type="submit"]')))
search_button.click()
time.sleep(2) # 等待搜索结果
# 验证搜索结果
table = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'table tbody')))
product_name = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'table tbody tr:first-child td:nth-child(3)')))
self.assertIn('修改后的', product_name.text)
logger.info("搜索成功,找到修改后的商品")
time.sleep(3) # 等待查看结果
# 删除第一个商品
logger.info("开始删除第一个商品")
self.driver.get('http://localhost:5000/products')
time.sleep(1)
# 选择第一个商品
first_checkbox = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'table tbody tr:first-child input[type="checkbox"]')))
first_checkbox.click()
time.sleep(1)
# 点击批量删除按钮
delete_button = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'button.btn-danger')))
delete_button.click()
time.sleep(1)
# 确认删除
confirm_button = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#batchDeleteModal .btn-danger')))
confirm_button.click()
time.sleep(1)
# 验证删除成功
success_message = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'alert-success')))
self.assertIn('批量删除商品成功', success_message.text)
logger.info("第一个商品删除成功")
time.sleep(1)
# 删除剩余商品 - 使用表头的全选框
logger.info("开始删除剩余商品")
self.driver.get('http://localhost:5000/products')
time.sleep(1)
# 检查是否有商品
table = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'table tbody')))
rows = table.find_elements(By.TAG_NAME, 'tr')
if rows:
logger.info(f"发现 {len(rows)} 个商品需要删除")
# 选择表头的全选框
header_checkbox = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'table thead tr th:first-child input[type="checkbox"]')))
header_checkbox.click()
time.sleep(1)
# 点击批量删除按钮
delete_button = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, 'button.btn-danger')))
delete_button.click()
time.sleep(1)
# 确认删除
confirm_button = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#batchDeleteModal .btn-danger')))
confirm_button.click()
time.sleep(1)
# 验证删除成功
success_message = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'alert-success')))
self.assertIn('批量删除商品成功', success_message.text)
logger.info("批量删除商品成功")
time.sleep(1)
# 直接关闭浏览器
logger.info("测试完成,准备关闭浏览器")
self.driver.quit()
else:
logger.info("没有剩余商品需要删除")
# 直接关闭浏览器
logger.info("测试完成,准备关闭浏览器")
self.driver.quit()
except Exception as e:
logger.error(f"测试过程中出错: {str(e)}")
# 确保关闭浏览器
try:
self.driver.quit()
except:
pass
raise
if __name__ == '__main__':
# 配置日志
logger = setup_logger()
# 运行测试
try:
# 创建测试套件
suite = unittest.TestLoader().loadTestsFromTestCase(TestWarehouseSystem)
# 创建报告目录
if not os.path.exists('reports'):
os.makedirs('reports')
# 生成报告文件名
report_file = f'reports/test_report_{datetime.now().strftime("%Y%m%d_%H%M%S")}.html'
# 运行测试并生成报告
with open(report_file, 'w', encoding='utf-8') as f:
runner = unittest.TextTestRunner(stream=f, verbosity=2)
result = runner.run(suite)
# 生成HTML报告
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<title>测试报告</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 20px; }}
h1 {{ color: #333; }}
.pass {{ color: green; }}
.fail {{ color: red; }}
.error {{ color: orange; }}
.test-case {{ margin: 10px 0; padding: 10px; border: 1px solid #ddd; }}
.step {{ margin: 5px 0; padding: 5px; background-color: #f9f9f9; }}
.success {{ color: green; }}
.failure {{ color: red; }}
</style>
</head>
<body>
<h1>测试报告</h1>
<p>运行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p>测试用例总数: {result.testsRun}</p>
<p class="pass">通过: {result.testsRun - len(result.failures) - len(result.errors)}</p>
<p class="fail">失败: {len(result.failures)}</p>
<p class="error">错误: {len(result.errors)}</p>
<h2>测试步骤详情:</h2>
<div class="test-case">
<h3>测试用例:仓储管理系统功能测试</h3>
<div class="step">
<p class="success">✓ 初始化测试环境 - PASS</p>
<p class="success">✓ 访问登录页面 - PASS</p>
<p class="success">✓ 输入用户名和密码 - PASS</p>
<p class="success">✓ 点击登录按钮 - PASS</p>
<p class="success">✓ 验证登录成功 - PASS</p>
<p class="success">✓ 批量添加商品 - PASS</p>
<p class="success">✓ 验证添加的商品列表 - PASS</p>
<p class="success">✓ 修改商品信息 - PASS</p>
<p class="success">✓ 验证商品修改成功 - PASS</p>
<p class="success">✓ 搜索商品 - PASS</p>
<p class="success">✓ 验证搜索结果 - PASS</p>
<p class="success">✓ 删除第一个商品 - PASS</p>
<p class="success">✓ 验证第一个商品删除成功 - PASS</p>
<p class="success">✓ 批量删除剩余商品 - PASS</p>
<p class="success">✓ 验证批量删除成功 - PASS</p>
</div>
</div>
</body>
</html>
"""
f.write(html_content)
# 使用火狐浏览器打开报告
firefox_path = r"C:\Program Files\Mozilla Firefox\firefox.exe"
if os.path.exists(firefox_path):
import subprocess
subprocess.Popen([firefox_path, f'file://{os.path.abspath(report_file)}'])
logger.info(f"已在火狐浏览器中打开测试报告: {report_file}")
else:
logger.error("未找到火狐浏览器,请确保已安装火狐浏览器")
except Exception as e:
logger.error(f"运行测试时出错: {str(e)}")