/** * 前端功能集成测试 * 用于验证 Param 绑定、EdgeType、暴露端口等功能 * * 使用方法: * 1. 在浏览器开发工具中粘贴此脚本 * 2. 调用测试函数,如 testParamBinding() */ // ============= 测试工具函数 ============= function log(...args) { console.log(`[TraceStudio Test] ${new Date().toLocaleTimeString()}`, ...args) } function success(msg) { console.log(`%c✅ ${msg}`, 'color: #22c55e; font-weight: bold') } function error(msg) { console.error(`%c❌ ${msg}`, 'color: #ef4444; font-weight: bold') } function warn(msg) { console.warn(`%c⚠️ ${msg}`, 'color: #f59e0b; font-weight: bold') } // ============= 1. Param 绑定测试 ============= async function testParamBinding() { log('开始测试:Param 绑定模式') // 从 DOM 查找 Inspector 并验证是否存在绑定模式按钮 const inspectorDiv = document.querySelector('[class*="Inspector"]') || document.body const modeButtons = inspectorDiv.querySelectorAll('button') const hasStaticBtn = Array.from(modeButtons).some(b => b.textContent.includes('静态值')) const hasContextBtn = Array.from(modeButtons).some(b => b.textContent.includes('Context')) const hasExposedBtn = Array.from(modeButtons).some(b => b.textContent.includes('暴露端口')) if (hasStaticBtn && hasContextBtn && hasExposedBtn) { success('发现三种 Param 绑定模式按钮') } else { error(`缺少绑定模式按钮: 静态=${hasStaticBtn}, Context=${hasContextBtn}, 暴露=${hasExposedBtn}`) } // 检查 Context 下拉列表 const contextSelects = inspectorDiv.querySelectorAll('select') if (contextSelects.length > 0) { const options = contextSelects[0].querySelectorAll('option') const hasGlobalOption = Array.from(options).some(o => o.value.includes('$Global')) if (hasGlobalOption) { success(`Context 下拉列表已初始化,共 ${options.length} 个选项`) } else { warn('Context 下拉列表缺少 $Global 变量') } } } // ============= 2. EdgeType 测试 ============= async function testEdgeType() { log('开始测试:连线 EdgeType 和 strokeWidth') // 从 React Flow 的 edge 元素中提取 edge 数据 const svgEdges = document.querySelectorAll('svg path[stroke]') const edgeData = { arrayEdges: 0, scalarEdges: 0, strokeWidths: new Set() } svgEdges.forEach((path) => { const strokeWidth = path.getAttribute('stroke-width') if (strokeWidth) { edgeData.strokeWidths.add(strokeWidth) if (parseInt(strokeWidth) === 4) edgeData.arrayEdges++ else if (parseInt(strokeWidth) === 3) edgeData.scalarEdges++ } }) if (edgeData.strokeWidths.size > 0) { success(`检测到 ${edgeData.arrayEdges} 条数组边(4px)和 ${edgeData.scalarEdges} 条标量边(3px)`) log(`检测到的 strokeWidth 值:${Array.from(edgeData.strokeWidths).join(', ')}`) } else { warn('未检测到任何连线,请先创建节点连接') } } // ============= 3. 暴露端口测试 ============= async function testExposedPorts() { log('开始测试:暴露端口显示') // 查找节点中的暴露端口指示符 const exposedIndicators = document.querySelectorAll('[style*="rgb(236, 72, 153)"]') if (exposedIndicators.length > 0) { success(`检测到 ${exposedIndicators.length} 个暴露端口指示区域`) } // 检查节点中的 Handle(React Flow 端口) const handles = document.querySelectorAll('.react-flow__handle') const exposedHandles = Array.from(handles).filter(h => { const style = h.getAttribute('style') || '' // 暴露端口应该是粉红色(#ec4899)或绿色(#22c55e) return style.includes('236') || style.includes('34, 197') || h.classList.contains('exposed') }) if (exposedHandles.length > 0) { success(`检测到 ${exposedHandles.length} 个暴露端口 Handle`) } else { warn('未检测到暴露端口,请先在 Inspector 中点击 ⚡ 暴露端口') } } // ============= 4. 节点数据结构测试 ============= async function testNodeDataStructure() { log('开始测试:节点数据结构(bindings, exposedPorts)') // 尝试从 window.__INITIAL_STATE__ 或其他全局状态中提取 let nodeData = null if (window.__TRACE_STUDIO_STATE__) { nodeData = window.__TRACE_STUDIO_STATE__.nodes } if (!nodeData) { warn('无法访问全局状态,使用 React DevTools 检查') log('请在 React DevTools 中查看 Store 的 nodes 属性') return } const firstNode = nodeData[0] if (firstNode) { const hasBindings = 'bindings' in firstNode const hasExposedPorts = 'exposedPorts' in firstNode if (hasBindings) success(`节点包含 bindings 字段`) else warn('节点缺少 bindings 字段') if (hasExposedPorts) success(`节点包含 exposedPorts 字段`) else warn('节点缺少 exposedPorts 字段') log('第一个节点结构:', firstNode) } } // ============= 5. API 响应测试 ============= async function testAPIResponse() { log('开始测试:API /api/plugins 响应') try { const response = await fetch('/api/plugins') const data = await response.json() if (response.ok) { success('API /api/plugins 响应成功') // 检查返回的插件是否包含扩展字段 const firstPlugin = Object.values(data.plugins || {})[0] if (firstPlugin) { const hasInputs = 'inputs' in firstPlugin const hasOutputs = 'outputs' in firstPlugin const hasParamSchema = 'param_schema' in firstPlugin const hasContextVars = 'context_vars' in firstPlugin log(`插件字段检查:inputs=${hasInputs}, outputs=${hasOutputs}, param_schema=${hasParamSchema}, context_vars=${hasContextVars}`) if (hasInputs && hasOutputs && hasParamSchema && hasContextVars) { success('API 已返回四大属性(inputs/outputs/param_schema/context_vars)') } else { warn('API 缺少某些字段') } } } else { error(`API 响应错误:${response.status}`) } } catch (e) { error(`API 请求失败:${e.message}`) } } // ============= 6. 完整工作流测试 ============= async function testCompleteWorkflow() { log('开始完整工作流测试') const tests = [ { name: '参数绑定', fn: testParamBinding }, { name: '连线 EdgeType', fn: testEdgeType }, { name: '暴露端口', fn: testExposedPorts }, { name: '数据结构', fn: testNodeDataStructure }, { name: 'API 响应', fn: testAPIResponse } ] for (const test of tests) { log(`\n--- 测试: ${test.name} ---`) try { await test.fn() } catch (e) { error(`测试失败:${e.message}`) } } log('\n✨ 所有测试完成') } // ============= 7. 交互式检查器 ============= function inspectStore() { log('检查 Zustand Store 状态') // 尝试从多个位置访问 store if (window.__ZUSTAND_DEBUG__) { const store = window.__ZUSTAND_DEBUG__ log('Store 节点:', store.getState?.().nodes || 'N/A') log('Store 边:', store.getState?.().edges || 'N/A') } else { log('Store 不可直接访问,请使用以下方法:') log('1. 打开 React DevTools') log('2. 选择 组件') log('3. 在控制台中查看 $r.props 或 $r.context') } } function inspectFirstNode() { log('检查第一个节点的详细信息') const nodeElement = document.querySelector('[data-id]') if (nodeElement) { const nodeId = nodeElement.getAttribute('data-id') log(`找到节点:${nodeId}`) // 提取节点的视觉信息 const handles = nodeElement.querySelectorAll('.react-flow__handle') const title = nodeElement.querySelector('h4') || nodeElement.querySelector('div') log(`节点标题:${title?.textContent || 'N/A'}`) log(`Handle 数量:${handles.length}`) // 分析 Handle 颜色 handles.forEach((h, i) => { const bg = h.style.background const position = h.classList.contains('target') ? 'LEFT' : 'RIGHT' log(` Handle ${i}: 颜色=${bg}, 位置=${position}`) }) } else { warn('未找到任何节点,请先创建节点') } } // ============= 导出测试函数 ============= window.testParamBinding = testParamBinding window.testEdgeType = testEdgeType window.testExposedPorts = testExposedPorts window.testNodeDataStructure = testNodeDataStructure window.testAPIResponse = testAPIResponse window.testCompleteWorkflow = testCompleteWorkflow window.inspectStore = inspectStore window.inspectFirstNode = inspectFirstNode // ============= 启动消息 ============= success('TraceStudio 测试工具已加载') log('可用的测试函数:') log(' testParamBinding() - Param 绑定模式') log(' testEdgeType() - 连线 EdgeType') log(' testExposedPorts() - 暴露端口') log(' testNodeDataStructure() - 节点数据结构') log(' testAPIResponse() - API 响应') log(' testCompleteWorkflow() - 完整工作流测试') log(' inspectStore() - 检查 Zustand Store') log(' inspectFirstNode() - 检查第一个节点') log('\n例如:testCompleteWorkflow() 运行全部测试')