TraceStudio-dev/web_v0/tests/TEST_INTEGRATION.js
2026-01-12 03:32:51 +08:00

277 lines
9.1 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 前端功能集成测试
* 用于验证 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} 个暴露端口指示区域`)
}
// 检查节点中的 HandleReact 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() 运行全部测试')