|
|
import json
|
|
|
import re
|
|
|
from datetime import datetime
|
|
|
from PyQt5.QtCore import Qt, QTimer, QAbstractTableModel, QModelIndex, QPoint, QSize
|
|
|
from PyQt5.QtGui import QFont, QBrush, QColor
|
|
|
from openpyxl import load_workbook
|
|
|
|
|
|
class ExcelParser:
|
|
|
@staticmethod
|
|
|
def parseProcedure(filePath):
|
|
|
wb = load_workbook(filename=filePath)
|
|
|
|
|
|
if '测试脚本' in wb.sheetnames:
|
|
|
sheet = wb['测试脚本']
|
|
|
else:
|
|
|
sheet = wb.active
|
|
|
|
|
|
specInfo = {
|
|
|
"规程名称": sheet['B1'].value or "",
|
|
|
"规程编号": sheet['D1'].value or "",
|
|
|
"规程类型": sheet['F1'].value or ""
|
|
|
}
|
|
|
|
|
|
testCaseInfo = {
|
|
|
"测试用例": sheet['B2'].value or "",
|
|
|
"用例编号": sheet['D2'].value or "",
|
|
|
"工况描述": sheet['H2'].value or ""
|
|
|
}
|
|
|
|
|
|
testSteps = []
|
|
|
currentStep = None
|
|
|
stepCounter = 0
|
|
|
|
|
|
for rowIdx in range(5, sheet.max_row + 1):
|
|
|
cellA = sheet[f'A{rowIdx}'].value
|
|
|
cellB = sheet[f'B{rowIdx}'].value
|
|
|
cellC = sheet[f'C{rowIdx}'].value
|
|
|
|
|
|
if cellB is None and cellA is None:
|
|
|
continue
|
|
|
|
|
|
if cellA and re.match(r'STEP\d+[::]', str(cellA)):
|
|
|
if currentStep:
|
|
|
testSteps.append(currentStep)
|
|
|
stepCounter += 1
|
|
|
|
|
|
stepName = str(cellA).replace(':', ':').strip()
|
|
|
stepDesc = str(cellB) if cellB else ""
|
|
|
|
|
|
currentStep = {
|
|
|
"步骤ID": stepName,
|
|
|
"步骤描述": stepDesc,
|
|
|
"操作类型": cellC if cellC else "",
|
|
|
"预期结果": sheet[f'D{rowIdx}'].value,
|
|
|
"子步骤": []
|
|
|
}
|
|
|
elif currentStep and cellA and str(cellA).isdigit():
|
|
|
subStep = {
|
|
|
"序号": int(cellA),
|
|
|
"操作": str(cellB) if cellB else "",
|
|
|
"操作类型": cellC if cellC else "",
|
|
|
"预期结果": sheet[f'D{rowIdx}'].value,
|
|
|
"实际结果": sheet[f'E{rowIdx}'].value,
|
|
|
"一致性": sheet[f'F{rowIdx}'].value if sheet[f'F{rowIdx}'].value == "是" else "否",
|
|
|
"测试时间": sheet[f'H{rowIdx}'].value,
|
|
|
"备注": sheet[f'I{rowIdx}'].value
|
|
|
}
|
|
|
currentStep["子步骤"].append(subStep)
|
|
|
|
|
|
if currentStep:
|
|
|
testSteps.append(currentStep)
|
|
|
|
|
|
return {
|
|
|
"文件路径": filePath,
|
|
|
"规程信息": {
|
|
|
"规程名称": specInfo["规程名称"],
|
|
|
"规程编号": specInfo["规程编号"],
|
|
|
"规程类型": specInfo["规程类型"]
|
|
|
},
|
|
|
"测试用例信息": {
|
|
|
"测试用例": testCaseInfo["测试用例"],
|
|
|
"用例编号": testCaseInfo["用例编号"],
|
|
|
"工况描述": testCaseInfo["工况描述"]
|
|
|
},
|
|
|
"测试步骤": testSteps
|
|
|
}
|
|
|
|
|
|
class StepTableModel(QAbstractTableModel):
|
|
|
columns = ['序号', '实验步骤','操作类型', '执行时间', '是否与预期一致', '实际结果', '备注']
|
|
|
|
|
|
def __init__(self, testSteps):
|
|
|
super().__init__()
|
|
|
self.stepData = []
|
|
|
self.stepIndex = 0
|
|
|
|
|
|
for mainStep in testSteps:
|
|
|
self.stepData.append({
|
|
|
'id': self.stepIndex,
|
|
|
'isMain': True,
|
|
|
'stepId': mainStep['步骤ID'],
|
|
|
'description': mainStep['步骤描述'],
|
|
|
'executed': False,
|
|
|
'stepType': mainStep['操作类型'],
|
|
|
'time': None,
|
|
|
'result': None,
|
|
|
'note': mainStep.get('备注', '') # 修复:从主步骤的备注字段读取
|
|
|
})
|
|
|
self.stepIndex += 1
|
|
|
|
|
|
for subStep in mainStep['子步骤']:
|
|
|
self.stepData.append({
|
|
|
'id': self.stepIndex,
|
|
|
'isMain': False,
|
|
|
'stepId': f"{mainStep['步骤ID']}{subStep['序号']}",
|
|
|
'description': subStep['操作'],
|
|
|
'stepType': subStep['操作类型'],
|
|
|
'executed': False,
|
|
|
'time': None,
|
|
|
'result': None,
|
|
|
'note': subStep['备注'] # 新增:从Excel解析的备注字段
|
|
|
})
|
|
|
self.stepIndex += 1
|
|
|
|
|
|
def rowCount(self, parent=QModelIndex()):
|
|
|
return len(self.stepData)
|
|
|
|
|
|
def columnCount(self, parent=QModelIndex()):
|
|
|
return len(self.columns)
|
|
|
|
|
|
def data(self, index, role=Qt.DisplayRole):
|
|
|
if not index.isValid():
|
|
|
return None
|
|
|
|
|
|
row = index.row()
|
|
|
col = index.column()
|
|
|
step = self.stepData[row]
|
|
|
|
|
|
if role == Qt.DisplayRole:
|
|
|
if col == 0:
|
|
|
return step['stepId'] or ''
|
|
|
elif col == 1:
|
|
|
return step['description'] or ''
|
|
|
elif col == 2:
|
|
|
return step['stepType'] or ''
|
|
|
elif col == 3:
|
|
|
return step['time'].strftime("%Y-%m-%d %H:%M:%S") if step['time'] else ''
|
|
|
elif col == 4:
|
|
|
# print(step['result'])
|
|
|
if step['executed']:
|
|
|
if '失败' in step['result']:
|
|
|
return '✘'
|
|
|
else:
|
|
|
return '✓'
|
|
|
elif col == 5:
|
|
|
# print(step['result'])
|
|
|
return step['result'] or ''
|
|
|
elif col == 6:
|
|
|
return step['note'] if step['note'] else ''
|
|
|
|
|
|
elif role == Qt.BackgroundRole:
|
|
|
if step['executed']:
|
|
|
if '失败' in step['result']:
|
|
|
return QBrush(QColor(255, 182, 193))
|
|
|
else:
|
|
|
return QBrush(QColor(144, 238, 144))
|
|
|
elif step['isMain']:
|
|
|
return QBrush(QColor(220, 220, 220))
|
|
|
|
|
|
elif role == Qt.TextAlignmentRole:
|
|
|
if col in [0, 2, 3, 4]: # 步骤ID、操作类型、执行时间、执行结果居中对齐
|
|
|
return Qt.AlignCenter
|
|
|
else: # 其他列左对齐并顶部对齐
|
|
|
return Qt.AlignLeft | Qt.AlignTop
|
|
|
|
|
|
elif role == Qt.FontRole and step['isMain']:
|
|
|
font = QFont()
|
|
|
font.setBold(True)
|
|
|
return font
|
|
|
|
|
|
# 新增:支持自动换行
|
|
|
elif role == Qt.TextAlignmentRole:
|
|
|
if col in [1, 5]: # 描述和备注列
|
|
|
return Qt.AlignLeft | Qt.AlignVCenter
|
|
|
elif role == Qt.TextWordWrap:
|
|
|
if col in [1, 5]:
|
|
|
return True
|
|
|
|
|
|
return None
|
|
|
|
|
|
def flags(self, index):
|
|
|
"""返回单元格标志"""
|
|
|
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
|
|
|
return flags
|
|
|
|
|
|
def headerData(self, section, orientation, role):
|
|
|
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
|
|
|
return self.columns[section]
|
|
|
return None
|
|
|
|
|
|
def getStepInfo(self, row):
|
|
|
if 0 <= row < len(self.stepData):
|
|
|
return self.stepData[row]
|
|
|
return None
|
|
|
|
|
|
def getFullStepInfo(self, row):
|
|
|
"""获取完整的步骤信息"""
|
|
|
if 0 <= row < len(self.stepData):
|
|
|
return self.stepData[row]
|
|
|
return None
|
|
|
|
|
|
def updateStepResult(self, row, result, time):
|
|
|
if 0 <= row < len(self.stepData):
|
|
|
# print(result)
|
|
|
self.stepData[row]['executed'] = True
|
|
|
self.stepData[row]['time'] = time
|
|
|
self.stepData[row]['result'] = result
|
|
|
self.dataChanged.emit(self.index(row, 0),
|
|
|
self.index(row, self.columnCount()-1),
|
|
|
[Qt.DisplayRole, Qt.BackgroundRole])
|
|
|
return True
|
|
|
return False
|
|
|
|
|
|
def resetExecutionState(self):
|
|
|
"""只重置执行状态(颜色),不清除时间和结果"""
|
|
|
for step in self.stepData:
|
|
|
step['executed'] = False
|
|
|
step['result'] = None
|
|
|
self.dataChanged.emit(self.index(0, 0),
|
|
|
self.index(self.rowCount()-1, self.columnCount()-1),
|
|
|
[Qt.BackgroundRole])
|
|
|
|
|
|
def resetAll(self):
|
|
|
"""完全重置所有状态(包括时间和结果)"""
|
|
|
for step in self.stepData:
|
|
|
step.update({
|
|
|
'executed': False,
|
|
|
'time': None,
|
|
|
'result': None
|
|
|
})
|
|
|
self.dataChanged.emit(self.index(0, 0),
|
|
|
self.index(self.rowCount()-1, self.columnCount()-1),
|
|
|
[Qt.DisplayRole, Qt.BackgroundRole])
|
|
|
|
|
|
def getTestSteps(self):
|
|
|
"""获取测试步骤数据,用于保存"""
|
|
|
testSteps = []
|
|
|
currentMainStep = None
|
|
|
|
|
|
for step in self.stepData:
|
|
|
if step['isMain']:
|
|
|
# 如果有前一个主步骤,先添加到结果中
|
|
|
if currentMainStep:
|
|
|
testSteps.append(currentMainStep)
|
|
|
|
|
|
# 创建新的主步骤
|
|
|
currentMainStep = {
|
|
|
'步骤ID': step['stepId'],
|
|
|
'步骤描述': step['description'],
|
|
|
'操作类型': step['stepType'],
|
|
|
'预期结果': '',
|
|
|
'子步骤': []
|
|
|
}
|
|
|
else:
|
|
|
# 子步骤
|
|
|
if currentMainStep:
|
|
|
# 从步骤ID中提取序号
|
|
|
stepId = step['stepId']
|
|
|
if currentMainStep['步骤ID'] in stepId:
|
|
|
subStepId = stepId.replace(currentMainStep['步骤ID'], '')
|
|
|
else:
|
|
|
subStepId = stepId
|
|
|
|
|
|
subStep = {
|
|
|
'序号': subStepId,
|
|
|
'操作': step['description'],
|
|
|
'操作类型': step['stepType'],
|
|
|
'预期结果': '',
|
|
|
'实际结果': step.get('result', ''),
|
|
|
'一致性': '是' if step.get('result') and '失败' not in step.get('result', '') else '否',
|
|
|
'测试时间': step.get('time', ''),
|
|
|
'备注': step.get('note', '')
|
|
|
}
|
|
|
currentMainStep['子步骤'].append(subStep)
|
|
|
|
|
|
# 添加最后一个主步骤
|
|
|
if currentMainStep:
|
|
|
testSteps.append(currentMainStep)
|
|
|
|
|
|
return testSteps
|