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;