|
|
|
|
@ -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 styles from './style/testInstance.module.less';
|
|
|
|
|
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 useWebSocket from '@/hooks/useWebSocket';
|
|
|
|
|
|
|
|
|
|
const CollapseItem = Collapse.Item;
|
|
|
|
|
|
|
|
|
|
@ -16,6 +23,42 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
|
|
|
|
|
const [selectedOperationIdent, setSelectedOperationIdent] = useState('');
|
|
|
|
|
const [activeOperationIdent, setActiveOperationIdent] = useState('');
|
|
|
|
|
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 res: any = await getComponentDesign(parentId);
|
|
|
|
|
@ -38,6 +81,42 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
|
|
|
|
|
getDesign();
|
|
|
|
|
}, [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) => {
|
|
|
|
|
setSelectedOperationIdent(operationIdent);
|
|
|
|
|
setEditingTestCase(null);
|
|
|
|
|
@ -62,7 +141,8 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
|
|
|
|
|
if (res.code === 200) {
|
|
|
|
|
Message.success('删除成功');
|
|
|
|
|
getTestCaseList();
|
|
|
|
|
} else {
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
Message.error(res.msg || '删除失败');
|
|
|
|
|
}
|
|
|
|
|
} 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 params = {
|
|
|
|
|
...values,
|
|
|
|
|
@ -100,16 +252,16 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
icon={<IconLink />}
|
|
|
|
|
onClick={() => setActiveTab('link')}
|
|
|
|
|
onClick={handleLinkInstance}
|
|
|
|
|
style={{ minWidth: 250 }}
|
|
|
|
|
>
|
|
|
|
|
链接实例
|
|
|
|
|
{isSocketConnected ? '断开实例' : '链接实例'}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles['tab-center']}>
|
|
|
|
|
<Button
|
|
|
|
|
type="outline"
|
|
|
|
|
onClick={() => setActiveTab('template')}
|
|
|
|
|
onClick={handleExportTemplate}
|
|
|
|
|
>
|
|
|
|
|
下载当前测试用例
|
|
|
|
|
</Button>
|
|
|
|
|
@ -119,15 +271,15 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
|
|
|
|
|
>
|
|
|
|
|
从模板导入用例
|
|
|
|
|
</Button>
|
|
|
|
|
{/*<Button*/}
|
|
|
|
|
{/* type="outline"*/}
|
|
|
|
|
{/* onClick={() => setActiveTab('batch')}*/}
|
|
|
|
|
{/*>*/}
|
|
|
|
|
{/* 一键生成测试用例*/}
|
|
|
|
|
{/*</Button>*/}
|
|
|
|
|
<Button
|
|
|
|
|
type="outline"
|
|
|
|
|
onClick={() => setActiveTab('batch')}
|
|
|
|
|
>
|
|
|
|
|
一键生成测试用例
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
type="outline"
|
|
|
|
|
onClick={() => setActiveTab('single')}
|
|
|
|
|
onClick={handleExportTestCases}
|
|
|
|
|
>
|
|
|
|
|
导出当前测试用例
|
|
|
|
|
</Button>
|
|
|
|
|
@ -294,18 +446,24 @@ const TestInstance = ({ instance, parentId, onBack }: { instance: any; parentId:
|
|
|
|
|
<div className={styles['panel-header']}>
|
|
|
|
|
<span>运行日志</span>
|
|
|
|
|
<div className={styles['header-actions']}>
|
|
|
|
|
<span className={styles['icon-btn']}>📋</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>
|
|
|
|
|
<span className={styles['icon-btn']} onClick={clearLogs} title="清空日志">🗑️</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles['log-content']}>
|
|
|
|
|
<div className={styles['log-empty']}>
|
|
|
|
|
<p>连接信息:连接成功!</p>
|
|
|
|
|
<p>未启动任务或任务已完成</p>
|
|
|
|
|
</div>
|
|
|
|
|
{logs.length === 0 ? (
|
|
|
|
|
<div className={styles['log-empty']}>
|
|
|
|
|
<p>暂无日志信息</p>
|
|
|
|
|
<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>
|
|
|
|
|
|