feat(test): 增加测试用例导出和WebSocket连接功能

- 新增导出测试用例模板和当前测试用例的功能
- 实现WebSocket连接以支持实时日志展示
- 添加日志面板用于显示测试过程中的消息
- 提供链接/断开测试实例的交互操作
- 优化UI布局,增加清空日志按钮
- 调整样式以适应新增的日志列表展示区域
master
钟良源 2 months ago
parent b8de1fcd87
commit 13b17c48db

@ -23,3 +23,18 @@ export function submitTestCase(params) {
export function deleteTestCase(ids) { export function deleteTestCase(ids) {
return axios.post(`${urlPrefix}/componentTestCase/remove?ids=${ids}`); return axios.post(`${urlPrefix}/componentTestCase/remove?ids=${ids}`);
} }
// 发送测试用例
export function sendTestCase(params) {
return axios.post(`${urlPrefix}/componentTestCase/handler`, params);
}
// 导出测试用例模板
export function exportTemplate(componentBaseId) {
return axios.get(`${urlPrefix}/componentTestCase/export-template/${componentBaseId}`, { responseType: 'blob' });
}
// 导出当前测试用例
export function exportTestCases(componentBaseId) {
return axios.get(`${urlPrefix}/componentTestCase/export/${componentBaseId}`, { responseType: 'blob' });
}

@ -381,11 +381,30 @@
color: #86909c; color: #86909c;
font-size: 13px; font-size: 13px;
line-height: 1.8; line-height: 1.8;
padding-top: 40px;
p { p {
margin: 8px 0; margin: 8px 0;
} }
} }
.log-list {
display: flex;
flex-direction: column;
gap: 4px;
.log-item {
padding: 8px 12px;
background: #f7f8fa;
border-radius: 4px;
font-size: 12px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
color: #4e5969;
line-height: 1.6;
word-break: break-all;
white-space: pre-wrap;
}
}
} }
} }
} }

@ -3,8 +3,15 @@ import { Button, Space, Tree, Collapse, Divider, Message, Modal } from '@arco-de
import { IconLeft, IconPlus, IconEdit, IconDelete, IconLink, IconSend } from '@arco-design/web-react/icon'; import { IconLeft, IconPlus, IconEdit, IconDelete, IconLink, IconSend } from '@arco-design/web-react/icon';
import styles from './style/testInstance.module.less'; import styles from './style/testInstance.module.less';
import { getComponentDesign } from '@/api/componentDevelopProcess'; import { getComponentDesign } from '@/api/componentDevelopProcess';
import { getComponentTestCaseList, submitTestCase, deleteTestCase } from '@/api/componentTestCase'; import {
getComponentTestCaseList,
submitTestCase,
deleteTestCase,
exportTemplate,
exportTestCases
} from '@/api/componentTestCase';
import TestCaseModal from './TestCaseModal'; import TestCaseModal from './TestCaseModal';
import useWebSocket from '@/hooks/useWebSocket';
const CollapseItem = Collapse.Item; const CollapseItem = Collapse.Item;
@ -16,6 +23,42 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
const [selectedOperationIdent, setSelectedOperationIdent] = useState(''); const [selectedOperationIdent, setSelectedOperationIdent] = useState('');
const [activeOperationIdent, setActiveOperationIdent] = useState(''); const [activeOperationIdent, setActiveOperationIdent] = useState('');
const [editingTestCase, setEditingTestCase] = useState(null); const [editingTestCase, setEditingTestCase] = useState(null);
const [logs, setLogs] = useState<string[]>([]);
const [isSocketConnected, setIsSocketConnected] = useState(false);
// WebSocket hook
const { connect, disconnect, isConnected, sendMessage } = useWebSocket({
onOpen: () => {
setIsSocketConnected(true);
addLog('连接信息:连接成功!');
},
onClose: () => {
setIsSocketConnected(false);
addLog('连接信息:连接已断开');
},
onError: () => {
addLog('连接错误:连接失败');
},
onMessage: (event) => {
try {
const data = JSON.parse(event.data);
addLog(`收到消息: ${JSON.stringify(data)}`);
} catch (e) {
addLog(`收到消息: ${event.data}`);
}
}
});
// 添加日志
const addLog = (message: string) => {
const timestamp = new Date().toLocaleTimeString();
setLogs(prev => [...prev, `[${timestamp}] ${message}`]);
};
// 清空日志
const clearLogs = () => {
setLogs([]);
};
const getDesign = async () => { const getDesign = async () => {
const res: any = await getComponentDesign(parentId); const res: any = await getComponentDesign(parentId);
@ -38,6 +81,42 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
getDesign(); getDesign();
}, [parentId, instance]); }, [parentId, instance]);
// 组件卸载时断开连接
useEffect(() => {
return () => {
disconnect();
};
}, [disconnect]);
const cryptoRandom = () => {
return new Date().getTime().toString(16) + Math.random().toString(16).substring(2);
};
// 链接实例
const handleLinkInstance = () => {
if (isSocketConnected) {
disconnect();
Message.info('已断开连接');
}
else {
// 构建WebSocket URL根据你的实际后端配置调整
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const host = window.location.host;
let wsUrl = '';
if (window.location.host.includes('localhost')) {
wsUrl = `ws://192.168.5.119/wst/test-case/${instance.id}/${cryptoRandom()}`;
}
else {
wsUrl = `${protocol}//${host}/wst/test-case/${instance.id}/${cryptoRandom()}`;
}
connect(wsUrl);
addLog('正在连接测试实例...');
}
};
const handleAddTestCase = (operationIdent: string) => { const handleAddTestCase = (operationIdent: string) => {
setSelectedOperationIdent(operationIdent); setSelectedOperationIdent(operationIdent);
setEditingTestCase(null); setEditingTestCase(null);
@ -62,7 +141,8 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
if (res.code === 200) { if (res.code === 200) {
Message.success('删除成功'); Message.success('删除成功');
getTestCaseList(); getTestCaseList();
} else { }
else {
Message.error(res.msg || '删除失败'); Message.error(res.msg || '删除失败');
} }
} catch (error) { } catch (error) {
@ -72,6 +152,78 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
}); });
}; };
// 导出测试用例模板
const handleExportTemplate = async () => {
try {
Message.loading('正在生成模板文件...');
const res: any = await exportTemplate(parentId);
// 创建下载链接
const blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
// 设置文件名
const fileName = `测试用例模板_${instance.identifier}_${new Date().getTime()}.xlsx`;
link.setAttribute('download', fileName);
// 触发下载
document.body.appendChild(link);
link.click();
// 清理
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
Message.success('模板下载成功');
} catch (error) {
console.error('导出模板失败:', error);
Message.error('导出模板失败');
}
};
// 导出当前测试用例
const handleExportTestCases = async () => {
try {
// 检查是否有测试用例
if (!testCaseList || testCaseList.length === 0) {
Message.warning('当前没有测试用例可导出');
return;
}
Message.loading('正在生成测试用例文件...');
const res: any = await exportTestCases(parentId);
// 创建下载链接
const blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
});
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
// 设置文件名
const fileName = `测试用例_${instance.identifier}_${new Date().getTime()}.xlsx`;
link.setAttribute('download', fileName);
// 触发下载
document.body.appendChild(link);
link.click();
// 清理
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
Message.success('测试用例导出成功');
} catch (error) {
console.error('导出测试用例失败:', error);
Message.error('导出测试用例失败');
}
};
const handleModalOk = async (values: any) => { const handleModalOk = async (values: any) => {
const params = { const params = {
...values, ...values,
@ -100,16 +252,16 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
<Button <Button
type="primary" type="primary"
icon={<IconLink />} icon={<IconLink />}
onClick={() => setActiveTab('link')} onClick={handleLinkInstance}
style={{ minWidth: 250 }} style={{ minWidth: 250 }}
> >
{isSocketConnected ? '断开实例' : '链接实例'}
</Button> </Button>
</div> </div>
<div className={styles['tab-center']}> <div className={styles['tab-center']}>
<Button <Button
type="outline" type="outline"
onClick={() => setActiveTab('template')} onClick={handleExportTemplate}
> >
</Button> </Button>
@ -119,15 +271,15 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
> >
</Button> </Button>
{/*<Button*/}
{/* type="outline"*/}
{/* onClick={() => setActiveTab('batch')}*/}
{/*>*/}
{/* 一键生成测试用例*/}
{/*</Button>*/}
<Button <Button
type="outline" type="outline"
onClick={() => setActiveTab('batch')} onClick={handleExportTestCases}
>
</Button>
<Button
type="outline"
onClick={() => setActiveTab('single')}
> >
</Button> </Button>
@ -294,18 +446,24 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
<div className={styles['panel-header']}> <div className={styles['panel-header']}>
<span></span> <span></span>
<div className={styles['header-actions']}> <div className={styles['header-actions']}>
<span className={styles['icon-btn']}>📋</span> <span className={styles['icon-btn']} onClick={clearLogs} title="清空日志">🗑</span>
<span className={styles['icon-btn']}>📁</span>
<span className={styles['icon-btn']}>💾</span>
<span className={styles['icon-btn']}>📊</span>
<span className={styles['icon-btn']}>🗑</span>
</div> </div>
</div> </div>
<div className={styles['log-content']}> <div className={styles['log-content']}>
<div className={styles['log-empty']}> {logs.length === 0 ? (
<p></p> <div className={styles['log-empty']}>
<p></p> <p></p>
</div> <p>"链接实例"</p>
</div>
) : (
<div className={styles['log-list']}>
{logs.map((log, index) => (
<div key={index} className={styles['log-item']}>
{log}
</div>
))}
</div>
)}
</div> </div>
</div> </div>
</div> </div>

Loading…
Cancel
Save