Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>AI TestGenX - Advanced Test Automation</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://unpkg.com/react@18/umd/react.development.js"></script> | |
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> | |
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.sidebar { | |
transition: all 0.3s ease; | |
} | |
.chart-container { | |
height: 300px; | |
} | |
.animate-pulse { | |
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
} | |
@keyframes pulse { | |
0%, 100% { | |
opacity: 1; | |
} | |
50% { | |
opacity: 0.5; | |
} | |
} | |
.test-card:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
} | |
.file-upload { | |
border: 2px dashed #e5e7eb; | |
} | |
.file-upload:hover { | |
border-color: #9ca3af; | |
} | |
</style> | |
</head> | |
<body class="bg-white"> | |
<div id="root"></div> | |
<script type="text/babel"> | |
const { useState, useEffect } = React; | |
function App() { | |
const [sidebarOpen, setSidebarOpen] = useState(true); | |
const [activeTab, setActiveTab] = useState('dashboard'); | |
const [testCases, setTestCases] = useState([]); | |
const [isGenerating, setIsGenerating] = useState(false); | |
const [executionStatus, setExecutionStatus] = useState('idle'); | |
const [testResults, setTestResults] = useState([]); | |
const [projectName, setProjectName] = useState('My Project'); | |
const [aiPrompt, setAiPrompt] = useState(''); | |
const [websiteUrl, setWebsiteUrl] = useState(''); | |
const [uploadedFile, setUploadedFile] = useState(null); | |
const [isAnalyzing, setIsAnalyzing] = useState(false); | |
const [analysisProgress, setAnalysisProgress] = useState(0); | |
const [failedTestsOnly, setFailedTestsOnly] = useState(false); | |
const [isDebugging, setIsDebugging] = useState(false); | |
const [debugSuggestions, setDebugSuggestions] = useState([]); | |
// Mock data initialization | |
useEffect(() => { | |
const mockTestCases = [ | |
{ id: 1, name: 'Login functionality', status: 'passed', generatedBy: 'AI', lastRun: '2 mins ago', type: 'UI' }, | |
{ id: 2, name: 'Checkout process', status: 'failed', generatedBy: 'AI', lastRun: '5 mins ago', type: 'API', error: 'Timeout waiting for payment button' }, | |
{ id: 3, name: 'Search feature', status: 'pending', generatedBy: 'Manual', lastRun: 'Never', type: 'UI' }, | |
{ id: 4, name: 'User profile update', status: 'passed', generatedBy: 'AI', lastRun: '10 mins ago', type: 'UI' }, | |
{ id: 5, name: 'Payment gateway integration', status: 'failed', generatedBy: 'AI', lastRun: 'Now', type: 'API', error: 'Invalid response from payment provider' }, | |
]; | |
setTestCases(mockTestCases); | |
const mockResults = [ | |
{ day: 'Mon', passed: 45, failed: 5 }, | |
{ day: 'Tue', passed: 52, failed: 3 }, | |
{ day: 'Wed', passed: 48, failed: 7 }, | |
{ day: 'Thu', passed: 56, failed: 2 }, | |
{ day: 'Fri', passed: 50, failed: 4 }, | |
{ day: 'Sat', passed: 40, failed: 1 }, | |
{ day: 'Sun', passed: 38, failed: 2 }, | |
]; | |
setTestResults(mockResults); | |
}, []); | |
const generateTests = () => { | |
setIsGenerating(true); | |
// Simulate AI generation | |
setTimeout(() => { | |
const newTest = { | |
id: testCases.length + 1, | |
name: `Test case for: ${aiPrompt || 'new feature'}`, | |
status: 'pending', | |
generatedBy: 'AI', | |
lastRun: 'Never', | |
type: aiPrompt.includes('API') ? 'API' : 'UI' | |
}; | |
setTestCases([...testCases, newTest]); | |
setIsGenerating(false); | |
setAiPrompt(''); | |
}, 2000); | |
}; | |
const generateFromUrl = () => { | |
if (!websiteUrl) return; | |
setIsGenerating(true); | |
// Simulate URL analysis and test generation | |
const interval = setInterval(() => { | |
setAnalysisProgress(prev => { | |
if (prev >= 100) { | |
clearInterval(interval); | |
return 100; | |
} | |
return prev + 10; | |
}); | |
}, 300); | |
setTimeout(() => { | |
const newTests = [ | |
{ | |
id: testCases.length + 1, | |
name: `Homepage load test for ${websiteUrl}`, | |
status: 'pending', | |
generatedBy: 'AI', | |
lastRun: 'Never', | |
type: 'UI' | |
}, | |
{ | |
id: testCases.length + 2, | |
name: `Navigation menu functionality for ${websiteUrl}`, | |
status: 'pending', | |
generatedBy: 'AI', | |
lastRun: 'Never', | |
type: 'UI' | |
}, | |
{ | |
id: testCases.length + 3, | |
name: `Form validation for ${websiteUrl}`, | |
status: 'pending', | |
generatedBy: 'AI', | |
lastRun: 'Never', | |
type: 'UI' | |
} | |
]; | |
setTestCases([...testCases, ...newTests]); | |
setIsGenerating(false); | |
setAnalysisProgress(0); | |
setWebsiteUrl(''); | |
}, 3000); | |
}; | |
const analyzeDocument = () => { | |
if (!uploadedFile) return; | |
setIsAnalyzing(true); | |
// Simulate document analysis | |
const interval = setInterval(() => { | |
setAnalysisProgress(prev => { | |
if (prev >= 100) { | |
clearInterval(interval); | |
return 100; | |
} | |
return prev + 10; | |
}); | |
}, 400); | |
setTimeout(() => { | |
const newTests = [ | |
{ | |
id: testCases.length + 1, | |
name: `Test for requirement: User authentication`, | |
status: 'pending', | |
generatedBy: 'AI', | |
lastRun: 'Never', | |
type: 'UI' | |
}, | |
{ | |
id: testCases.length + 2, | |
name: `Test for requirement: Data validation`, | |
status: 'pending', | |
generatedBy: 'AI', | |
lastRun: 'Never', | |
type: 'API' | |
}, | |
{ | |
id: testCases.length + 3, | |
name: `Test for requirement: Performance thresholds`, | |
status: 'pending', | |
generatedBy: 'AI', | |
lastRun: 'Never', | |
type: 'Performance' | |
} | |
]; | |
setTestCases([...testCases, ...newTests]); | |
setIsAnalyzing(false); | |
setAnalysisProgress(0); | |
setUploadedFile(null); | |
}, 4000); | |
}; | |
const executeTests = (onlyFailed = false) => { | |
setExecutionStatus('running'); | |
setFailedTestsOnly(onlyFailed); | |
// Simulate test execution | |
setTimeout(() => { | |
const updatedTests = testCases.map(test => { | |
if (onlyFailed && test.status !== 'failed') return test; | |
return { | |
...test, | |
status: Math.random() > 0.2 ? 'passed' : 'failed', | |
lastRun: 'Just now', | |
...(Math.random() > 0.7 ? { error: `Error code: ${Math.floor(Math.random() * 1000)}` } : {}) | |
}; | |
}); | |
setTestCases(updatedTests); | |
setExecutionStatus('completed'); | |
}, 3000); | |
}; | |
const debugFailedTests = () => { | |
setIsDebugging(true); | |
// Simulate AI debugging | |
setTimeout(() => { | |
const suggestions = [ | |
{ | |
testId: 2, | |
suggestion: "Increase wait time for payment button from 5s to 10s", | |
confidence: "85%" | |
}, | |
{ | |
testId: 5, | |
suggestion: "Add retry logic for payment API calls (3 attempts)", | |
confidence: "78%" | |
}, | |
{ | |
testId: 5, | |
suggestion: "Verify API response schema matches documentation", | |
confidence: "92%" | |
} | |
]; | |
setDebugSuggestions(suggestions); | |
setIsDebugging(false); | |
}, 2500); | |
}; | |
const applyDebugSuggestion = (suggestion) => { | |
// Simulate applying the suggestion | |
const updatedTests = testCases.map(test => { | |
if (test.id === suggestion.testId) { | |
return { | |
...test, | |
status: 'pending', | |
lastRun: 'Never', | |
debugApplied: suggestion.suggestion | |
}; | |
} | |
return test; | |
}); | |
setTestCases(updatedTests); | |
setDebugSuggestions(debugSuggestions.filter(s => s !== suggestion)); | |
}; | |
const getStatusColor = (status) => { | |
switch (status) { | |
case 'passed': return 'bg-gray-100 text-gray-800 border border-green-500'; | |
case 'failed': return 'bg-gray-100 text-gray-800 border border-red-500'; | |
case 'running': return 'bg-gray-100 text-gray-800 border border-blue-500'; | |
case 'pending': return 'bg-gray-100 text-gray-800 border border-yellow-500'; | |
default: return 'bg-gray-100 text-gray-800'; | |
} | |
}; | |
const getTypeColor = (type) => { | |
return type === 'UI' ? 'bg-gray-100 text-gray-800 border border-gray-300' : | |
type === 'API' ? 'bg-gray-100 text-gray-800 border border-gray-400' : | |
'bg-gray-100 text-gray-800 border border-gray-500'; | |
}; | |
const TestChart = () => { | |
const maxValue = Math.max(...testResults.map(r => r.passed + r.failed)); | |
return ( | |
<div className="chart-container bg-white p-4 border border-gray-200 rounded"> | |
<h3 className="text-lg font-medium mb-4 text-gray-800">Test Execution Trends</h3> | |
<div className="flex items-end h-48 space-x-2"> | |
{testResults.map((result, index) => { | |
const total = result.passed + result.failed; | |
const passedHeight = (result.passed / maxValue) * 100; | |
const failedHeight = (result.failed / maxValue) * 100; | |
return ( | |
<div key={index} className="flex flex-col items-center flex-1"> | |
<div className="flex flex-col-reverse w-full h-40"> | |
<div | |
className="bg-gray-800 rounded-t" | |
style={{ height: `${failedHeight}%` }} | |
></div> | |
<div | |
className="bg-gray-300 rounded-t" | |
style={{ height: `${passedHeight}%` }} | |
></div> | |
</div> | |
<span className="text-xs mt-2 text-gray-600">{result.day}</span> | |
</div> | |
); | |
})} | |
</div> | |
<div className="flex justify-center mt-4 space-x-4"> | |
<div className="flex items-center"> | |
<div className="w-3 h-3 bg-gray-300 rounded-full mr-1"></div> | |
<span className="text-xs text-gray-600">Passed</span> | |
</div> | |
<div className="flex items-center"> | |
<div className="w-3 h-3 bg-gray-800 rounded-full mr-1"></div> | |
<span className="text-xs text-gray-600">Failed</span> | |
</div> | |
</div> | |
</div> | |
); | |
}; | |
return ( | |
<div className="flex h-screen overflow-hidden bg-white"> | |
{/* Sidebar */} | |
<div className={`sidebar ${sidebarOpen ? 'w-64' : 'w-20'} bg-black text-white transition-all duration-300 flex flex-col`}> | |
<div className="p-4 flex items-center justify-between border-b border-gray-800"> | |
{sidebarOpen ? ( | |
<h1 className="text-xl font-bold text-white">AI TestGenX</h1> | |
) : ( | |
<div className="w-8 h-8 bg-white rounded-full flex items-center justify-center"> | |
<i className="fas fa-robot text-black"></i> | |
</div> | |
)} | |
<button | |
onClick={() => setSidebarOpen(!sidebarOpen)} | |
className="text-gray-400 hover:text-white" | |
> | |
<i className={`fas ${sidebarOpen ? 'fa-times' : 'fa-bars'}`}></i> | |
</button> | |
</div> | |
<nav className="flex-1 p-4 space-y-2"> | |
<button | |
onClick={() => setActiveTab('dashboard')} | |
className={`w-full flex items-center p-3 rounded ${activeTab === 'dashboard' ? 'bg-gray-800 text-white' : 'hover:bg-gray-800 text-gray-300'}`} | |
> | |
<i className="fas fa-tachometer-alt mr-3"></i> | |
{sidebarOpen && <span>Dashboard</span>} | |
</button> | |
<button | |
onClick={() => setActiveTab('tests')} | |
className={`w-full flex items-center p-3 rounded ${activeTab === 'tests' ? 'bg-gray-800 text-white' : 'hover:bg-gray-800 text-gray-300'}`} | |
> | |
<i className="fas fa-vial mr-3"></i> | |
{sidebarOpen && <span>Test Cases</span>} | |
</button> | |
<button | |
onClick={() => setActiveTab('generator')} | |
className={`w-full flex items-center p-3 rounded ${activeTab === 'generator' ? 'bg-gray-800 text-white' : 'hover:bg-gray-800 text-gray-300'}`} | |
> | |
<i className="fas fa-magic mr-3"></i> | |
{sidebarOpen && <span>AI Generator</span>} | |
</button> | |
<button | |
onClick={() => setActiveTab('execution')} | |
className={`w-full flex items-center p-3 rounded ${activeTab === 'execution' ? 'bg-gray-800 text-white' : 'hover:bg-gray-800 text-gray-300'}`} | |
> | |
<i className="fas fa-play-circle mr-3"></i> | |
{sidebarOpen && <span>Execution</span>} | |
</button> | |
<button | |
onClick={() => setActiveTab('reports')} | |
className={`w-full flex items-center p-3 rounded ${activeTab === 'reports' ? 'bg-gray-800 text-white' : 'hover:bg-gray-800 text-gray-300'}`} | |
> | |
<i className="fas fa-chart-bar mr-3"></i> | |
{sidebarOpen && <span>Reports</span>} | |
</button> | |
</nav> | |
<div className="p-4 border-t border-gray-800"> | |
<div className="flex items-center"> | |
<div className="w-10 h-10 rounded-full bg-gray-700 flex items-center justify-center"> | |
<i className="fas fa-user text-gray-300"></i> | |
</div> | |
{sidebarOpen && ( | |
<div className="ml-3"> | |
<p className="text-sm font-medium text-white">John Doe</p> | |
<p className="text-xs text-gray-400">Admin</p> | |
</div> | |
)} | |
</div> | |
</div> | |
</div> | |
{/* Main Content */} | |
<div className="flex-1 overflow-auto"> | |
{/* Header */} | |
<header className="bg-white border-b border-gray-200"> | |
<div className="px-6 py-4 flex justify-between items-center"> | |
<h2 className="text-xl font-medium text-gray-900"> | |
{activeTab === 'dashboard' && 'Dashboard'} | |
{activeTab === 'tests' && 'Test Cases'} | |
{activeTab === 'generator' && 'AI Test Generator'} | |
{activeTab === 'execution' && 'Test Execution'} | |
{activeTab === 'reports' && 'Test Reports'} | |
</h2> | |
<div className="flex items-center space-x-4"> | |
<div className="relative"> | |
<input | |
type="text" | |
placeholder="Search..." | |
className="pl-10 pr-4 py-2 border border-gray-300 rounded focus:outline-none focus:ring-1 focus:ring-gray-400 focus:border-gray-400 bg-white" | |
/> | |
<i className="fas fa-search absolute left-3 top-3 text-gray-400"></i> | |
</div> | |
<button className="p-2 text-gray-600 hover:text-gray-900"> | |
<i className="fas fa-bell"></i> | |
</button> | |
</div> | |
</div> | |
</header> | |
{/* Content Area */} | |
<main className="p-6 bg-gray-50"> | |
{activeTab === 'dashboard' && ( | |
<div className="space-y-6"> | |
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
<div className="bg-white p-6 border border-gray-200 rounded"> | |
<div className="flex justify-between items-start"> | |
<div> | |
<p className="text-sm text-gray-600">Total Test Cases</p> | |
<h3 className="text-2xl font-semibold mt-2 text-gray-800">{testCases.length}</h3> | |
</div> | |
<div className="bg-gray-100 p-3 rounded-full"> | |
<i className="fas fa-vial text-gray-600"></i> | |
</div> | |
</div> | |
</div> | |
<div className="bg-white p-6 border border-gray-200 rounded"> | |
<div className="flex justify-between items-start"> | |
<div> | |
<p className="text-sm text-gray-600">Passed Tests</p> | |
<h3 className="text-2xl font-semibold mt-2 text-gray-800"> | |
{testCases.filter(t => t.status === 'passed').length} | |
</h3> | |
</div> | |
<div className="bg-gray-100 p-3 rounded-full text-green-500"> | |
<i className="fas fa-check-circle"></i> | |
</div> | |
</div> | |
</div> | |
<div className="bg-white p-6 border border-gray-200 rounded"> | |
<div className="flex justify-between items-start"> | |
<div> | |
<p className="text-sm text-gray-600">Failed Tests</p> | |
<h3 className="text-2xl font-semibold mt-2 text-gray-800"> | |
{testCases.filter(t => t.status === 'failed').length} | |
</h3> | |
</div> | |
<div className="bg-gray-100 p-3 rounded-full text-red-500"> | |
<i className="fas fa-times-circle"></i> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> | |
<TestChart /> | |
<div className="bg-white p-6 border border-gray-200 rounded"> | |
<h3 className="text-lg font-medium mb-4 text-gray-800">Recent Activities</h3> | |
<div className="space-y-4"> | |
{testCases.slice(0, 5).map(test => ( | |
<div key={test.id} className="flex items-start"> | |
<div className={`p-2 rounded-full mr-3 ${getStatusColor(test.status)}`}> | |
{test.status === 'passed' && <i className="fas fa-check text-green-500"></i>} | |
{test.status === 'failed' && <i className="fas fa-times text-red-500"></i>} | |
{test.status === 'running' && <i className="fas fa-sync-alt fa-spin text-blue-500"></i>} | |
{test.status === 'pending' && <i className="fas fa-clock text-yellow-500"></i>} | |
</div> | |
<div className="flex-1"> | |
<p className="font-medium text-gray-800">{test.name}</p> | |
<p className="text-sm text-gray-500"> | |
{test.generatedBy === 'AI' ? 'AI Generated' : 'Manual'} • {test.lastRun} | |
</p> | |
</div> | |
<span className={`text-xs px-2 py-1 rounded ${getTypeColor(test.type)}`}> | |
{test.type} | |
</span> | |
</div> | |
))} | |
</div> | |
</div> | |
</div> | |
</div> | |
)} | |
{activeTab === 'tests' && ( | |
<div className="bg-white border border-gray-200 rounded overflow-hidden"> | |
<div className="p-4 border-b border-gray-200 flex justify-between items-center"> | |
<h3 className="text-lg font-medium text-gray-800">All Test Cases</h3> | |
<div className="flex space-x-3"> | |
<button | |
onClick={() => setActiveTab('generator')} | |
className="flex items-center px-4 py-2 bg-black text-white rounded hover:bg-gray-800" | |
> | |
<i className="fas fa-plus mr-2"></i> | |
<span>Generate with AI</span> | |
</button> | |
<button className="flex items-center px-4 py-2 border border-gray-300 rounded hover:bg-gray-50"> | |
<i className="fas fa-filter mr-2"></i> | |
<span>Filter</span> | |
</button> | |
</div> | |
</div> | |
<div className="overflow-x-auto"> | |
<table className="min-w-full divide-y divide-gray-200"> | |
<thead className="bg-gray-50"> | |
<tr> | |
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th> | |
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th> | |
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th> | |
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Generated By</th> | |
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Run</th> | |
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> | |
</tr> | |
</thead> | |
<tbody className="bg-white divide-y divide-gray-200"> | |
{testCases.map(test => ( | |
<tr key={test.id} className="hover:bg-gray-50"> | |
<td className="px-6 py-4 whitespace-nowrap"> | |
<div className="flex items-center"> | |
<div className="flex-shrink-0 h-10 w-10 bg-gray-100 rounded-full flex items-center justify-center"> | |
<i className="fas fa-vial text-gray-600"></i> | |
</div> | |
<div className="ml-4"> | |
<div className="text-sm font-medium text-gray-900">{test.name}</div> | |
{test.debugApplied && ( | |
<div className="text-xs text-gray-500 mt-1"> | |
<i className="fas fa-lightbulb text-yellow-500 mr-1"></i> | |
{test.debugApplied} | |
</div> | |
)} | |
</div> | |
</div> | |
</td> | |
<td className="px-6 py-4 whitespace-nowrap"> | |
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded ${getTypeColor(test.type)}`}> | |
{test.type} | |
</span> | |
</td> | |
<td className="px-6 py-4 whitespace-nowrap"> | |
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded ${getStatusColor(test.status)}`}> | |
{test.status} | |
</span> | |
</td> | |
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> | |
{test.generatedBy === 'AI' ? ( | |
<span className="flex items-center"> | |
<i className="fas fa-robot text-gray-600 mr-1"></i> | |
AI | |
</span> | |
) : 'Manual'} | |
</td> | |
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{test.lastRun}</td> | |
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium"> | |
<button className="text-gray-600 hover:text-gray-900 mr-3"> | |
<i className="fas fa-play"></i> | |
</button> | |
<button className="text-gray-600 hover:text-gray-900 mr-3"> | |
<i className="fas fa-edit"></i> | |
</button> | |
<button className="text-gray-600 hover:text-gray-900"> | |
<i className="fas fa-trash"></i> | |
</button> | |
</td> | |
</tr> | |
))} | |
</tbody> | |
</table> | |
</div> | |
</div> | |
)} | |
{activeTab === 'generator' && ( | |
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
<div className="lg:col-span-2 space-y-6"> | |
<div className="bg-white p-6 border border-gray-200 rounded"> | |
<h3 className="text-xl font-medium mb-4 text-gray-800">AI Test Generator</h3> | |
<div className="mb-6"> | |
<label className="block text-sm font-medium text-gray-700 mb-2">Project Name</label> | |
<input | |
type="text" | |
value={projectName} | |
onChange={(e) => setProjectName(e.target.value)} | |
className="w-full px-4 py-2 border border-gray-300 rounded focus:ring-1 focus:ring-gray-400 focus:border-gray-400" | |
/> | |
</div> | |
<div className="mb-6"> | |
<label className="block text-sm font-medium text-gray-700 mb-2"> | |
Describe what you want to test | |
</label> | |
<textarea | |
value={aiPrompt} | |
onChange={(e) => setAiPrompt(e.target.value)} | |
rows="5" | |
placeholder="Example: 'Generate UI tests for login page with email and password fields, including validation for incorrect credentials'" | |
className="w-full px-4 py-2 border border-gray-300 rounded focus:ring-1 focus:ring-gray-400 focus:border-gray-400" | |
></textarea> | |
</div> | |
<div className="flex justify-end"> | |
<button | |
onClick={generateTests} | |
disabled={isGenerating} | |
className="px-6 py-3 bg-black text-white rounded hover:bg-gray-800 disabled:opacity-50 flex items-center" | |
> | |
{isGenerating ? ( | |
<> | |
<i className="fas fa-spinner fa-spin mr-2"></i> | |
Generating... | |
</> | |
) : ( | |
<> | |
<i className="fas fa-magic mr-2"></i> | |
Generate Test Cases | |
</> | |
)} | |
</button> | |
</div> | |
</div> | |
<div className="bg-white p-6 border border-gray-200 rounded"> | |
<h3 className="text-xl font-medium mb-4 text-gray-800">Generate from Website URL</h3> | |
<div className="mb-6"> | |
<label className="block text-sm font-medium text-gray-700 mb-2">Website URL</label> | |
<input | |
type="url" | |
value={websiteUrl} | |
onChange={(e) => setWebsiteUrl(e.target.value)} | |
placeholder="https://example.com" | |
className="w-full px-4 py-2 border border-gray-300 rounded focus:ring-1 focus:ring-gray-400 focus:border-gray-400" | |
/> | |
</div> | |
{isGenerating && ( | |
<div className="mb-4"> | |
<div className="flex justify-between mb-1"> | |
<span className="text-sm font-medium text-gray-700">Analyzing website...</span> | |
<span className="text-sm font-medium text-gray-700">{analysisProgress}%</span> | |
</div> | |
<div className="w-full bg-gray-200 rounded-full h-2.5"> | |
<div | |
className="bg-gray-800 h-2.5 rounded-full" | |
style={{ width: `${analysisProgress}%` }} | |
></div> | |
</div> | |
</div> | |
)} | |
<div className="flex justify-end"> | |
<button | |
onClick={generateFromUrl} | |
disabled={isGenerating || !websiteUrl} | |
className="px-6 py-3 bg-black text-white rounded hover:bg-gray-800 disabled:opacity-50 flex items-center" | |
> | |
{isGenerating ? ( | |
<> | |
<i className="fas fa-spinner fa-spin mr-2"></i> | |
Analyzing... | |
</> | |
) : ( | |
<> | |
<i className="fas fa-globe mr-2"></i> | |
Generate from URL | |
</> | |
)} | |
</button> | |
</div> | |
</div> | |
<div className="bg-white p-6 border border-gray-200 rounded"> | |
<h3 className="text-xl font-medium mb-4 text-gray-800">Generate from Business Requirements</h3> | |
<div className="mb-6"> | |
<label className="block text-sm font-medium text-gray-700 mb-2">Upload Document</label> | |
<div className="file-upload rounded-lg p-8 text-center cursor-pointer hover:bg-gray-50 transition-colors"> | |
{uploadedFile ? ( | |
<div className="flex items-center justify-center"> | |
<i className="fas fa-file-alt text-gray-500 mr-2"></i> | |
<span className="text-gray-700">{uploadedFile.name}</span> | |
<button | |
onClick={(e) => { | |
e.stopPropagation(); | |
setUploadedFile(null); | |
}} | |
className="ml-2 text-gray-500 hover:text-gray-700" | |
> | |
<i className="fas fa-times"></i> | |
</button> | |
</div> | |
) : ( | |
<div> | |
<i className="fas fa-cloud-upload-alt text-gray-400 text-4xl mb-2"></i> | |
<p className="text-sm text-gray-500 mb-1">Drag & drop your document here</p> | |
<p className="text-xs text-gray-400">or click to browse (PDF, DOCX, TXT)</p> | |
<input | |
type="file" | |
className="hidden" | |
id="file-upload" | |
onChange={(e) => setUploadedFile(e.target.files[0])} | |
/> | |
<label | |
htmlFor="file-upload" | |
className="inline-block mt-3 px-4 py-2 bg-gray-100 text-gray-700 rounded hover:bg-gray-200 cursor-pointer" | |
> | |
Select File | |
</label> | |
</div> | |
)} | |
</div> | |
</div> | |
{isAnalyzing && ( | |
<div className="mb-4"> | |
<div className="flex justify-between mb-1"> | |
<span className="text-sm font-medium text-gray-700">Analyzing document...</span> | |
<span className="text-sm font-medium text-gray-700">{analysisProgress}%</span> | |
</div> | |
<div className="w-full bg-gray-200 rounded-full h-2.5"> | |
<div | |
className="bg-gray-800 h-2.5 rounded-full" | |
style={{ width: `${analysisProgress}%` }} | |
></div> | |
</div> | |
</div> | |
)} | |
<div className="flex justify-end"> | |
<button | |
onClick={analyzeDocument} | |
disabled={isAnalyzing || !uploadedFile} | |
className="px-6 py-3 bg-black text-white rounded hover:bg-gray-800 disabled:opacity-50 flex items-center" | |
> | |
{isAnalyzing ? ( | |
<> | |
<i className="fas fa-spinner fa-spin mr-2"></i> | |
Processing... | |
</> | |
) : ( | |
<> | |
<i className="fas fa-file-import mr-2"></i> | |
Generate from Document | |
</> | |
)} | |
</button> | |
</div> | |
</div> | |
</div> | |
<div className="bg-white p-6 border border-gray-200 rounded"> | |
<h3 className="text-xl font-medium mb-4 text-gray-800">AI Suggestions</h3> | |
<div className="space-y-4"> | |
<div | |
className="p-4 border border-gray-200 rounded cursor-pointer hover:bg-gray-50 transition-colors" | |
onClick={() => setAiPrompt("Generate UI tests for login page with email and password fields, including validation for incorrect credentials")} | |
> | |
<h4 className="font-medium text-gray-800">Login Page Tests</h4> | |
<p className="text-sm text-gray-600 mt-1">Comprehensive UI tests for login functionality</p> | |
</div> | |
<div | |
className="p-4 border border-gray-200 rounded cursor-pointer hover:bg-gray-50 transition-colors" | |
onClick={() => setAiPrompt("Create API tests for user registration endpoint with validation for required fields")} | |
> | |
<h4 className="font-medium text-gray-800">Registration API Tests</h4> | |
<p className="text-sm text-gray-600 mt-1">Endpoint validation and error handling</p> | |
</div> | |
<div | |
className="p-4 border border-gray-200 rounded cursor-pointer hover:bg-gray-50 transition-colors" | |
onClick={() => setAiPrompt("Generate performance tests for product search functionality with 100 concurrent users")} | |
> | |
<h4 className="font-medium text-gray-800">Performance Tests</h4> | |
<p className="text-sm text-gray-600 mt-1">Load testing for critical features</p> | |
</div> | |
<div | |
className="p-4 border border-gray-200 rounded cursor-pointer hover:bg-gray-50 transition-colors" | |
onClick={() => setAiPrompt("Create accessibility tests for homepage to check WCAG 2.1 AA compliance")} | |
> | |
<h4 className="font-medium text-gray-800">Accessibility Tests</h4> | |
<p className="text-sm text-gray-600 mt-1">WCAG compliance validation</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
)} | |
{activeTab === 'execution' && ( | |
<div className="space-y-6"> | |
<div className="bg-white p-6 border border-gray-200 rounded"> | |
<div className="flex justify-between items-center mb-6"> | |
<h3 className="text-xl font-medium text-gray-800">Test Execution</h3> | |
<div className="flex space-x-3"> | |
<button | |
onClick={() => executeTests(false)} | |
disabled={executionStatus === 'running' || testCases.length === 0} | |
className={`px-4 py-2 rounded flex items-center ${executionStatus === 'running' && !failedTestsOnly ? 'bg-gray-800 text-white' : 'bg-black text-white hover:bg-gray-800'} disabled:opacity-50`} | |
> | |
{executionStatus === 'running' && !failedTestsOnly ? ( | |
<> | |
<i className="fas fa-sync-alt fa-spin mr-2"></i> | |
Running Tests... | |
</> | |
) : ( | |
<> | |
<i className="fas fa-play mr-2"></i> | |
Run All Tests | |
</> | |
)} | |
</button> | |
<button | |
onClick={() => executeTests(true)} | |
disabled={executionStatus === 'running' || testCases.filter(t => t.status === 'failed').length === 0} | |
className={`px-4 py-2 rounded flex items-center ${executionStatus === 'running' && failedTestsOnly ? 'bg-gray-800 text-white' : 'bg-gray-700 text-white hover:bg-gray-600'} disabled:opacity-50`} | |
> | |
{executionStatus === 'running' && failedTestsOnly ? ( | |
<> | |
<i className="fas fa-sync-alt fa-spin mr-2"></i> | |
Debugging... | |
</> | |
) : ( | |
<> | |
<i className="fas fa-bug mr-2"></i> | |
Debug Failed Only | |
</> | |
)} | |
</button> | |
<button | |
onClick={debugFailedTests} | |
disabled={isDebugging || testCases.filter(t => t.status === 'failed').length === 0} | |
className="px-4 py-2 border border-gray-300 rounded hover:bg-gray-50 flex items-center disabled:opacity-50" | |
> | |
{isDebugging ? ( | |
<> | |
<i className="fas fa-spinner fa-spin mr-2"></i> | |
Analyzing... | |
</> | |
) : ( | |
<> | |
<i className="fas fa-lightbulb mr-2"></i> | |
AI Debug Suggestions | |
</> | |
)} | |
</button> | |
</div> | |
</div> | |
{debugSuggestions.length > 0 && ( | |
<div className="mb-6 bg-gray-50 p-4 rounded border border-gray-200"> | |
<h4 className="font-medium text-gray-800 mb-3 flex items-center"> | |
<i className="fas fa-robot text-gray-600 mr-2"></i> | |
AI Debugging Suggestions | |
</h4> | |
<div className="space-y-3"> | |
{debugSuggestions.map((suggestion, index) => ( | |
<div key={index} className="flex justify-between items-center p-3 bg-white border border-gray-200 rounded"> | |
<div> | |
<p className="font-medium text-gray-800"> | |
Test #{suggestion.testId}: {suggestion.suggestion} | |
</p> | |
<p className="text-xs text-gray-500 mt-1"> | |
Confidence: {suggestion.confidence} | |
</p> | |
</div> | |
<button | |
onClick={() => applyDebugSuggestion(suggestion)} | |
className="px-3 py-1 bg-black text-white text-sm rounded hover:bg-gray-800" | |
> | |
Apply | |
</button> | |
</div> | |
))} | |
</div> | |
</div> | |
)} | |
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6"> | |
<div className="border border-gray-200 rounded p-4"> | |
<div className="flex justify-between items-center"> | |
<div> | |
<p className="text-sm text-gray-600">Total to Run</p> | |
<p className="text-xl font-semibold text-gray-800"> | |
{failedTestsOnly ? | |
testCases.filter(t => t.status === 'failed').length : | |
testCases.length} | |
</p> | |
</div> | |
<div className="bg-gray-100 p-3 rounded-full"> | |
<i className="fas fa-list text-gray-600"></i> | |
</div> | |
</div> | |
</div> | |
<div className="border border-gray-200 rounded p-4"> | |
<div className="flex justify-between items-center"> | |
<div> | |
<p className="text-sm text-gray-600">Currently Running</p> | |
<p className="text-xl font-semibold text-blue-500"> | |
{executionStatus === 'running' ? testCases.filter(t => t.status === 'running').length : 0} | |
</p> | |
</div> | |
<div className="bg-gray-100 p-3 rounded-full text-blue-500"> | |
<i className="fas fa-sync-alt"></i> | |
</div> | |
</div> | |
</div> | |
<div className="border border-gray-200 rounded p-4"> | |
<div className="flex justify-between items-center"> | |
<div> | |
<p className="text-sm text-gray-600">Estimated Time</p> | |
<p className="text-xl font-semibold text-gray-800"> | |
~{failedTestsOnly ? | |
testCases.filter(t => t.status === 'failed').length * 2 : | |
testCases.length * 2}s | |
</p> | |
</div> | |
<div className="bg-gray-100 p-3 rounded-full text-gray-600"> | |
<i className="fas fa-clock"></i> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="overflow-x-auto"> | |
<table className="min-w-full divide-y divide-gray-200"> | |
<thead className="bg-gray-50"> | |
<tr> | |
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Test Case</th> | |
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th> | |
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Progress</th> | |
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Duration</th> | |
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Details</th> | |
</tr> | |
</thead> | |
<tbody className="bg-white divide-y divide-gray-200"> | |
{testCases | |
.filter(test => !failedTestsOnly || test.status === 'failed') | |
.map(test => ( | |
<tr key={test.id}> | |
<td className="px-6 py-4 whitespace-nowrap"> | |
<div className="text-sm font-medium text-gray-900">{test.name}</div> | |
<div className="text-sm text-gray-500">{test.type}</div> | |
{test.error && ( | |
<div className="text-xs text-red-500 mt-1"> | |
<i className="fas fa-exclamation-circle mr-1"></i> | |
{test.error} | |
</div> | |
)} | |
</td> | |
<td className="px-6 py-4 whitespace-nowrap"> | |
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded ${getStatusColor(test.status)}`}> | |
{test.status} | |
</span> | |
</td> | |
<td className="px-6 py-4 whitespace-nowrap"> | |
{test.status === 'running' ? ( | |
<div className="w-full bg-gray-200 rounded-full h-2"> | |
<div | |
className="bg-blue-500 h-2 rounded-full animate-pulse" | |
style={{ width: '45%' }} | |
></div> | |
</div> | |
) : test.status === 'pending' ? ( | |
<div className="w-full bg-gray-200 rounded-full h-2"></div> | |
) : ( | |
<div className="w-full bg-gray-200 rounded-full h-2"> | |
<div | |
className={`h-2 rounded-full ${test.status === 'passed' ? 'bg-green-500' : 'bg-red-500'}`} | |
style={{ width: '100%' }} | |
></div> | |
</div> | |
)} | |
</td> | |
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> | |
{test.status === 'running' ? 'In progress' : | |
test.status === 'pending' ? '--' : '2.4s'} | |
</td> | |
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium"> | |
<button className="text-gray-600 hover:text-gray-900"> | |
View Logs | |
</button> | |
</td> | |
</tr> | |
))} | |
</tbody> | |
</table> | |
</div> | |
</div> | |
</div> | |
)} | |
{activeTab === 'reports' && ( | |
<div className="space-y-6"> | |
<div className="bg-white p-6 border border-gray-200 rounded"> | |
<h3 className="text-xl font-medium mb-6 text-gray-800">Test Reports</h3> | |
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8"> | |
<div className="border border-gray-200 rounded p-4"> | |
<div className="flex justify-between"> | |
<div> | |
<p className="text-sm text-gray-600">Total Executions</p> | |
<p className="text-xl font-semibold text-gray-800">127</p> | |
</div> | |
<div className="text-green-500"> | |
<i className="fas fa-arrow-up"></i> 12% | |
</div> | |
</div> | |
<div className="mt-3 h-1 bg-gray-200 rounded-full"> | |
<div className="h-1 bg-gray-800 rounded-full" style={{ width: '70%' }}></div> | |
</div> | |
</div> | |
<div className="border border-gray-200 rounded p-4"> | |
<div className="flex justify-between"> | |
<div> | |
<p className="text-sm text-gray-600">Pass Rate</p> | |
<p className="text-xl font-semibold text-gray-800">92%</p> | |
</div> | |
<div className="text-green-500"> | |
<i className="fas fa-arrow-up"></i> 5% | |
</div> | |
</div> | |
<div className="mt-3 h-1 bg-gray-200 rounded-full"> | |
<div className="h-1 bg-gray-300 rounded-full" style={{ width: '92%' }}></div> | |
</div> | |
</div> | |
<div className="border border-gray-200 rounded p-4"> | |
<div className="flex justify-between"> | |
<div> | |
<p className="text-sm text-gray-600">Flaky Tests</p> | |
<p className="text-xl font-semibold text-gray-800">4</p> | |
</div> | |
<div className="text-red-500"> | |
<i className="fas fa-arrow-down"></i> 2 | |
</div> | |
</div> | |
<div className="mt-3 h-1 bg-gray-200 rounded-full"> | |
<div className="h-1 bg-yellow-500 rounded-full" style={{ width: '15%' }}></div> | |
</div> | |
</div> | |
<div className="border border-gray-200 rounded p-4"> | |
<div className="flex justify-between"> | |
<div> | |
<p className="text-sm text-gray-600">Avg. Duration</p> | |
<p className="text-xl font-semibold text-gray-800">3.2s</p> | |
</div> | |
<div className="text-green-500"> | |
<i className="fas fa-arrow-down"></i> 0.8s | |
</div> | |
</div> | |
<div className="mt-3 h-1 bg-gray-200 rounded-full"> | |
<div className="h-1 bg-blue-500 rounded-full" style={{ width: '40%' }}></div> | |
</div> | |
</div> | |
</div> | |
<TestChart /> | |
<div className="mt-8"> | |
<h4 className="text-lg font-medium mb-4 text-gray-800">Failed Tests Analysis</h4> | |
<div className="bg-gray-50 border border-gray-200 rounded p-4"> | |
<div className="flex items-start"> | |
<div className="bg-gray-100 p-3 rounded-full mr-4"> | |
<i className="fas fa-exclamation-triangle text-gray-600"></i> | |
</div> | |
<div> | |
<h5 className="font-medium text-gray-800">Most Common Failure Patterns</h5> | |
<ul className="mt-2 space-y-2"> | |
<li className="flex items-center"> | |
<span className="w-2 h-2 bg-gray-800 rounded-full mr-2"></span> | |
<span>Timeout waiting for elements (42%)</span> | |
</li> | |
<li className="flex items-center"> | |
<span className="w-2 h-2 bg-gray-800 rounded-full mr-2"></span> | |
<span>API response validation (33%)</span> | |
</li> | |
<li className="flex items-center"> | |
<span className="w-2 h-2 bg-gray-800 rounded-full mr-2"></span> | |
<span>State management issues (25%)</span> | |
</li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
)} | |
</main> | |
</div> | |
</div> | |
); | |
} | |
ReactDOM.render(<App />, document.getElementById('root')); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=anonymousatom/tmp-ai" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |