Spaces:
Sleeping
Sleeping
File size: 10,612 Bytes
a70baf7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | import React, { useState } from 'react';
import { Shield, Play, Terminal, CheckCircle2, AlertCircle, Trash, Plus, FileText, Settings, Loader2 } from 'lucide-react';
interface QaTestFlow {
id: string;
name: string;
url: string;
steps: string[];
status: 'passed' | 'failed' | 'idle' | 'running';
runtime?: string;
}
const INITIAL_FLOWS: QaTestFlow[] = [
{
id: '1',
name: 'User Onboarding Pipeline',
url: 'https://huggingface.co/join',
steps: [
"act('fill email with test@example.com')",
"act('click #submit-btn')",
"act_get('check if verify message is visible')"
],
status: 'passed',
runtime: '3.4s'
},
{
id: '2',
name: 'Model Space Launch Flow',
url: 'https://huggingface.co/spaces',
steps: [
"act('click .new-space-button')",
"act('fill #space-name with my-super-agent')",
"act_get('assert checkout status is active')"
],
status: 'failed',
runtime: '5.1s'
}
];
const QaTestingTab: React.FC = () => {
const [flows, setFlows] = useState<QaTestFlow[]>(INITIAL_FLOWS);
const [selectedFlow, setSelectedFlow] = useState<QaTestFlow | null>(INITIAL_FLOWS[0]);
const [isExecuting, setIsExecuting] = useState(false);
const [newStep, setNewStep] = useState('');
const [consoleLogs, setConsoleLogs] = useState<string[]>([]);
const handleRunFlow = (flow: QaTestFlow) => {
setIsExecuting(true);
setConsoleLogs([
`[${new Date().toLocaleTimeString()}] Initializing QA Test Suite: ${flow.name}...`,
`[${new Date().toLocaleTimeString()}] Setting up sandbox browser context...`,
`[${new Date().toLocaleTimeString()}] Target: ${flow.url}`
]);
// Update state to running
setFlows(prev => prev.map(f => f.id === flow.id ? { ...f, status: 'running' } : f));
// Simulate steps execution
let currentIdx = 0;
const runInterval = setInterval(() => {
if (currentIdx < flow.steps.length) {
const step = flow.steps[currentIdx];
setConsoleLogs(prev => [...prev, `[EXEC] Running SDK command: ${step}... OK`]);
currentIdx++;
} else {
clearInterval(runInterval);
setIsExecuting(false);
const randomOutcome = Math.random() > 0.15 ? 'passed' : 'failed';
setFlows(prev => prev.map(f => f.id === flow.id ? { ...f, status: randomOutcome, runtime: '4.8s' } : f));
setConsoleLogs(prev => [
...prev,
`[RESULT] Suite completed. Outcome: ${randomOutcome.toUpperCase()}`,
`[REPORT] PDF Report generated successfully.`
]);
// Update local detail state
if (selectedFlow?.id === flow.id) {
setSelectedFlow(prev => prev ? { ...prev, status: randomOutcome, runtime: '4.8s' } : null);
}
}
}, 1000);
};
const handleAddStep = () => {
if (!newStep.trim() || !selectedFlow) return;
const updatedFlow = {
...selectedFlow,
steps: [...selectedFlow.steps, newStep]
};
setFlows(prev => prev.map(f => f.id === selectedFlow.id ? updatedFlow : f));
setSelectedFlow(updatedFlow);
setNewStep('');
};
return (
<div className="max-w-7xl mx-auto px-6 py-10 text-white grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Test cases selection panel */}
<div className="bg-[#0c0c0c] border border-gray-800 rounded-2xl p-6 space-y-6 h-fit">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Shield className="text-red-400" size={20} />
<h2 className="font-bold text-base">QA Test Suite</h2>
</div>
<span className="text-xs bg-gray-900 border border-gray-800 text-gray-400 px-2 py-0.5 rounded-full">
{flows.length} Flows
</span>
</div>
<div className="space-y-2">
{flows.map((flow) => (
<div
key={flow.id}
onClick={() => setSelectedFlow(flow)}
className={`p-4 rounded-xl cursor-pointer border transition-all flex items-center justify-between ${
selectedFlow?.id === flow.id
? 'bg-red-950/10 border-red-500/30 text-white'
: 'bg-black/40 border-gray-900 hover:border-gray-800 text-gray-300'
}`}
>
<div className="space-y-1 max-w-[70%]">
<h4 className="font-semibold text-sm truncate">{flow.name}</h4>
<p className="text-[10px] text-gray-500 truncate">{flow.url}</p>
</div>
<div className="flex items-center gap-2">
{flow.status === 'passed' && <CheckCircle2 size={16} className="text-green-500" />}
{flow.status === 'failed' && <AlertCircle size={16} className="text-red-500" />}
{flow.status === 'running' && <Loader2 size={16} className="text-red-400 animate-spin" />}
{flow.status === 'idle' && <span className="w-2 h-2 rounded-full bg-gray-600"></span>}
<button
onClick={(e) => {
e.stopPropagation();
handleRunFlow(flow);
}}
disabled={isExecuting}
className="p-1.5 bg-gray-900 border border-gray-800 hover:border-gray-700 text-red-400 hover:text-white rounded-lg transition-all"
>
<Play size={10} />
</button>
</div>
</div>
))}
</div>
<button className="w-full py-3 bg-red-950/20 border border-red-900/30 text-red-400 text-xs font-bold rounded-xl hover:bg-red-950/40 transition-all flex items-center justify-center gap-1.5">
<Plus size={14} />
Create New Flow Test
</button>
</div>
{/* Execution panel and visual steps editor */}
<div className="lg:col-span-2 bg-[#0c0c0c] border border-gray-800 rounded-2xl p-6 flex flex-col justify-between min-h-[500px]">
{selectedFlow ? (
<div className="space-y-6 flex-1">
<div className="border-b border-gray-800 pb-4 flex justify-between items-start">
<div>
<h3 className="text-lg font-bold text-gray-100">{selectedFlow.name}</h3>
<p className="text-xs text-gray-500 font-mono mt-1">Target: {selectedFlow.url}</p>
</div>
<div className="flex items-center gap-3">
{selectedFlow.runtime && (
<span className="text-[10px] bg-gray-900 border border-gray-800 px-2 py-1 rounded font-mono text-gray-400">
Runtime: {selectedFlow.runtime}
</span>
)}
<button
onClick={() => handleRunFlow(selectedFlow)}
disabled={isExecuting}
className="px-4 py-1.5 bg-red-600 hover:bg-red-500 text-white text-xs font-bold rounded-lg transition-all flex items-center gap-1.5 shadow-lg shadow-red-900/20 disabled:opacity-50"
>
<Play size={10} />
Execute Suite
</button>
</div>
</div>
{/* Test script steps list */}
<div className="space-y-2">
<label className="text-xs text-gray-400 font-bold uppercase tracking-wider">Test Automation Instructions (act & act_get)</label>
<div className="space-y-1">
{selectedFlow.steps.map((step, idx) => (
<div key={idx} className="flex items-center justify-between bg-black/50 border border-gray-900 rounded-xl px-4 py-3 text-xs font-mono text-gray-300">
<div className="flex gap-3">
<span className="text-red-500/80 font-bold">{idx + 1}</span>
<span>{step}</span>
</div>
<button className="text-gray-600 hover:text-red-400 transition-colors">
<Trash size={12} />
</button>
</div>
))}
</div>
{/* Add instruction input */}
<div className="flex gap-2 pt-2">
<input
type="text"
value={newStep}
onChange={(e) => setNewStep(e.target.value)}
className="flex-1 bg-black border border-gray-800 rounded-xl p-3 text-xs font-mono text-white outline-none focus:border-red-500"
placeholder="e.g. act('click .submit')"
/>
<button
onClick={handleAddStep}
className="px-4 py-3 bg-gray-900 border border-gray-800 hover:border-gray-700 rounded-xl text-xs font-bold hover:text-white transition-all flex items-center gap-1"
>
<Plus size={14} /> Add
</button>
</div>
</div>
{/* Execution logs output */}
<div className="space-y-2 pt-4">
<label className="text-xs text-gray-500 font-bold uppercase tracking-wider flex items-center gap-1.5">
<Terminal size={14} />
Live Execution Logs
</label>
<div className="w-full h-36 bg-black border border-gray-800 rounded-xl p-3 font-mono text-[10px] text-red-400/90 overflow-y-auto space-y-1">
{consoleLogs.length === 0 ? (
<span className="text-gray-600 italic">No logging information. Execute flow test to spin up the runner logs.</span>
) : (
consoleLogs.map((log, idx) => <p key={idx}>{log}</p>)
)}
</div>
</div>
</div>
) : (
<div className="flex-1 flex items-center justify-center text-sm text-gray-500 italic">
Select a test flow from the side panel to view scripts and start assertions.
</div>
)}
{/* Reports Download Footer */}
{selectedFlow && selectedFlow.status !== 'idle' && selectedFlow.status !== 'running' && (
<div className="border-t border-gray-900 pt-4 mt-6 flex items-center justify-between text-xs text-gray-500">
<span className="flex items-center gap-1"><FileText size={12} /> HTML & XML Reports Ready</span>
<button className="flex items-center gap-1 text-red-400 font-bold hover:underline">
<Settings size={12} /> Download Raw Artifacts
</button>
</div>
)}
</div>
</div>
);
};
export default QaTestingTab; |