277 lines
9.1 KiB
JavaScript
277 lines
9.1 KiB
JavaScript
|
|
/**
|
|||
|
|
* 前端功能集成测试
|
|||
|
|
* 用于验证 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. 选择 <Workspace /> 组件')
|
|||
|
|
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() 运行全部测试')
|