@nbakka/mcp-appium 2.0.99 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,774 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Test Case Review & Approval</title>
7
- <style>
8
- * {
9
- box-sizing: border-box;
10
- margin: 0;
11
- padding: 0;
12
- }
13
-
14
- body {
15
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
- background-color: #f8f9fa;
17
- line-height: 1.6;
18
- color: #333;
19
- }
20
-
21
- .container {
22
- max-width: 1400px;
23
- margin: 0 auto;
24
- padding: 20px;
25
- }
26
-
27
- .header {
28
- text-align: center;
29
- margin-bottom: 30px;
30
- padding: 20px;
31
- background: white;
32
- border-radius: 10px;
33
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
34
- }
35
-
36
- .header h1 {
37
- color: #2c3e50;
38
- margin-bottom: 10px;
39
- }
40
-
41
- .session-info {
42
- color: #7f8c8d;
43
- font-size: 14px;
44
- }
45
-
46
- .section {
47
- margin-bottom: 30px;
48
- background: white;
49
- border-radius: 10px;
50
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
51
- overflow: hidden;
52
- }
53
-
54
- .section-header {
55
- padding: 20px;
56
- font-weight: bold;
57
- font-size: 18px;
58
- border-bottom: 3px solid;
59
- }
60
-
61
- .new-section .section-header {
62
- background-color: #d4edda;
63
- color: #155724;
64
- border-bottom-color: #28a745;
65
- }
66
-
67
- .modify-section .section-header {
68
- background-color: #fff3cd;
69
- color: #856404;
70
- border-bottom-color: #ffc107;
71
- }
72
-
73
- .remove-section .section-header {
74
- background-color: #f8d7da;
75
- color: #721c24;
76
- border-bottom-color: #dc3545;
77
- }
78
-
79
- .section-content {
80
- padding: 20px;
81
- }
82
-
83
- .test-case {
84
- border: 1px solid #e9ecef;
85
- border-radius: 8px;
86
- margin-bottom: 15px;
87
- background: #f8f9fa;
88
- transition: all 0.3s ease;
89
- }
90
-
91
- .test-case:hover {
92
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
93
- }
94
-
95
- .test-case-header {
96
- padding: 15px;
97
- background: white;
98
- border-bottom: 1px solid #e9ecef;
99
- display: flex;
100
- justify-content: space-between;
101
- align-items: center;
102
- border-radius: 8px 8px 0 0;
103
- }
104
-
105
- .test-case-id {
106
- font-family: 'Courier New', monospace;
107
- background: #e9ecef;
108
- padding: 4px 8px;
109
- border-radius: 4px;
110
- font-size: 12px;
111
- color: #495057;
112
- }
113
-
114
- .test-case-actions {
115
- display: flex;
116
- gap: 8px;
117
- }
118
-
119
- .btn {
120
- padding: 8px 16px;
121
- border: none;
122
- border-radius: 6px;
123
- cursor: pointer;
124
- font-size: 13px;
125
- font-weight: 500;
126
- transition: all 0.2s ease;
127
- text-transform: uppercase;
128
- letter-spacing: 0.5px;
129
- }
130
-
131
- .btn:hover {
132
- transform: translateY(-1px);
133
- box-shadow: 0 2px 8px rgba(0,0,0,0.2);
134
- }
135
-
136
- .btn-edit {
137
- background: #007bff;
138
- color: white;
139
- }
140
-
141
- .btn-edit:hover {
142
- background: #0056b3;
143
- }
144
-
145
- .btn-delete {
146
- background: #dc3545;
147
- color: white;
148
- }
149
-
150
- .btn-delete:hover {
151
- background: #c82333;
152
- }
153
-
154
- .btn-save {
155
- background: #28a745;
156
- color: white;
157
- }
158
-
159
- .btn-save:hover {
160
- background: #218838;
161
- }
162
-
163
- .btn-cancel {
164
- background: #6c757d;
165
- color: white;
166
- }
167
-
168
- .btn-cancel:hover {
169
- background: #545b62;
170
- }
171
-
172
- .test-case-content {
173
- padding: 15px;
174
- }
175
-
176
- .original-text {
177
- background: #f8d7da;
178
- border: 1px solid #f1aeb5;
179
- border-radius: 4px;
180
- padding: 10px;
181
- margin-bottom: 10px;
182
- text-decoration: line-through;
183
- color: #721c24;
184
- font-style: italic;
185
- }
186
-
187
- .modified-text {
188
- background: #d1ecf1;
189
- border: 1px solid #b8daff;
190
- border-radius: 4px;
191
- padding: 10px;
192
- color: #0c5460;
193
- font-weight: 500;
194
- }
195
-
196
- .description-text {
197
- background: #d4edda;
198
- border: 1px solid #c3e6cb;
199
- border-radius: 4px;
200
- padding: 10px;
201
- color: #155724;
202
- }
203
-
204
- .remove-text {
205
- background: #f8d7da;
206
- border: 1px solid #f1aeb5;
207
- border-radius: 4px;
208
- padding: 10px;
209
- color: #721c24;
210
- }
211
-
212
- .edit-form {
213
- background: #e9ecef;
214
- border-radius: 6px;
215
- padding: 15px;
216
- }
217
-
218
- .form-group {
219
- margin-bottom: 15px;
220
- }
221
-
222
- .form-label {
223
- display: block;
224
- margin-bottom: 5px;
225
- font-weight: 600;
226
- color: #495057;
227
- }
228
-
229
- .form-control {
230
- width: 100%;
231
- padding: 10px;
232
- border: 2px solid #ced4da;
233
- border-radius: 6px;
234
- font-family: inherit;
235
- font-size: 14px;
236
- resize: vertical;
237
- min-height: 80px;
238
- }
239
-
240
- .form-control:focus {
241
- outline: none;
242
- border-color: #007bff;
243
- box-shadow: 0 0 0 3px rgba(0,123,255,0.1);
244
- }
245
-
246
- .form-control[readonly] {
247
- background-color: #f8f9fa;
248
- cursor: not-allowed;
249
- }
250
-
251
- .empty-section {
252
- text-align: center;
253
- padding: 40px;
254
- color: #6c757d;
255
- font-style: italic;
256
- }
257
-
258
- .approval-section {
259
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
260
- color: white;
261
- text-align: center;
262
- padding: 30px;
263
- border-radius: 10px;
264
- margin-top: 30px;
265
- }
266
-
267
- .approval-buttons {
268
- display: flex;
269
- justify-content: center;
270
- gap: 20px;
271
- margin-top: 20px;
272
- }
273
-
274
- .btn-primary {
275
- background: #28a745;
276
- color: white;
277
- padding: 15px 30px;
278
- font-size: 16px;
279
- font-weight: bold;
280
- }
281
-
282
- .btn-primary:hover {
283
- background: #218838;
284
- }
285
-
286
- .btn-danger {
287
- background: #dc3545;
288
- color: white;
289
- padding: 15px 30px;
290
- font-size: 16px;
291
- font-weight: bold;
292
- }
293
-
294
- .btn-danger:hover {
295
- background: #c82333;
296
- }
297
-
298
- .status-message {
299
- margin-top: 20px;
300
- padding: 15px;
301
- border-radius: 6px;
302
- font-weight: 500;
303
- text-align: center;
304
- }
305
-
306
- .status-success {
307
- background: #d4edda;
308
- color: #155724;
309
- border: 1px solid #c3e6cb;
310
- }
311
-
312
- .status-error {
313
- background: #f8d7da;
314
- color: #721c24;
315
- border: 1px solid #f1aeb5;
316
- }
317
-
318
- .status-info {
319
- background: #d1ecf1;
320
- color: #0c5460;
321
- border: 1px solid #b8daff;
322
- }
323
-
324
- .loading {
325
- text-align: center;
326
- padding: 60px;
327
- font-size: 18px;
328
- color: #6c757d;
329
- }
330
-
331
- .loading::after {
332
- content: '...';
333
- animation: dots 1.5s steps(5, end) infinite;
334
- }
335
-
336
- @keyframes dots {
337
- 0%, 20% { color: rgba(0,0,0,0); text-shadow: .25em 0 0 rgba(0,0,0,0), .5em 0 0 rgba(0,0,0,0); }
338
- 40% { color: #6c757d; text-shadow: .25em 0 0 rgba(0,0,0,0), .5em 0 0 rgba(0,0,0,0); }
339
- 60% { text-shadow: .25em 0 0 #6c757d, .5em 0 0 rgba(0,0,0,0); }
340
- 80%, 100% { text-shadow: .25em 0 0 #6c757d, .5em 0 0 #6c757d; }
341
- }
342
-
343
- .fade-in {
344
- animation: fadeIn 0.5s ease-in;
345
- }
346
-
347
- @keyframes fadeIn {
348
- from { opacity: 0; transform: translateY(20px); }
349
- to { opacity: 1; transform: translateY(0); }
350
- }
351
- </style>
352
- </head>
353
- <body>
354
- <div class="container">
355
- <div class="header">
356
- <h1>🧪 Test Case Review & Approval</h1>
357
- <div class="session-info" id="sessionInfo">
358
- Loading session information...
359
- </div>
360
- </div>
361
-
362
- <div id="loading" class="loading">
363
- Loading test cases
364
- </div>
365
-
366
- <div id="content" style="display: none;">
367
- <div id="newSection" class="section new-section">
368
- <div class="section-header">
369
- ✨ New Test Cases (<span id="newCount">0</span>)
370
- </div>
371
- <div class="section-content" id="newCases">
372
- <div class="empty-section">No new test cases</div>
373
- </div>
374
- </div>
375
-
376
- <div id="modifySection" class="section modify-section">
377
- <div class="section-header">
378
- 🔄 Modified Test Cases (<span id="modifyCount">0</span>)
379
- </div>
380
- <div class="section-content" id="modifyCases">
381
- <div class="empty-section">No modified test cases</div>
382
- </div>
383
- </div>
384
-
385
- <div id="removeSection" class="section remove-section">
386
- <div class="section-header">
387
- 🗑️ Test Cases to Remove (<span id="removeCount">0</span>)
388
- </div>
389
- <div class="section-content" id="removeCases">
390
- <div class="empty-section">No test cases to remove</div>
391
- </div>
392
- </div>
393
-
394
- <div class="approval-section">
395
- <h2>🎯 Review Complete?</h2>
396
- <p>Review all test cases above and make any necessary changes, then approve or cancel.</p>
397
- <div class="approval-buttons">
398
- <button class="btn btn-primary" onclick="approveTestCases()">
399
- ✅ Approve All Test Cases
400
- </button>
401
- <button class="btn btn-danger" onclick="cancelReview()">
402
- ❌ Cancel Review
403
- </button>
404
- </div>
405
- <div id="statusMessage" class="status-message" style="display: none;"></div>
406
- </div>
407
- </div>
408
- </div>
409
-
410
- <script>
411
- // Global state
412
- let sessionId = null;
413
- let testCases = { new: [], modify: [], remove: [] };
414
- let editingStates = new Set();
415
-
416
- // Utility functions
417
- function showStatus(message, type = 'info') {
418
- const statusEl = document.getElementById('statusMessage');
419
- statusEl.textContent = message;
420
- statusEl.className = `status-message status-${type}`;
421
- statusEl.style.display = 'block';
422
-
423
- if (type === 'success') {
424
- setTimeout(() => {
425
- statusEl.style.display = 'none';
426
- }, 3000);
427
- }
428
- }
429
-
430
- function updateCounts() {
431
- document.getElementById('newCount').textContent = testCases.new?.length || 0;
432
- document.getElementById('modifyCount').textContent = testCases.modify?.length || 0;
433
- document.getElementById('removeCount').textContent = testCases.remove?.length || 0;
434
- }
435
-
436
- // API functions
437
- async function apiCall(url, options = {}) {
438
- try {
439
- const response = await fetch(url, {
440
- headers: {
441
- 'Content-Type': 'application/json',
442
- ...options.headers
443
- },
444
- ...options
445
- });
446
-
447
- if (!response.ok) {
448
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
449
- }
450
-
451
- return await response.json();
452
- } catch (error) {
453
- console.error('API call failed:', error);
454
- throw error;
455
- }
456
- }
457
-
458
- // Load initial data
459
- async function loadTestCases() {
460
- try {
461
- const data = await apiCall('/api/testcases');
462
- sessionId = data.sessionId;
463
- testCases = data.testCases || { new: [], modify: [], remove: [] };
464
-
465
- document.getElementById('sessionInfo').textContent =
466
- `Session ID: ${sessionId} | Total Cases: ${
467
- (testCases.new?.length || 0) +
468
- (testCases.modify?.length || 0) +
469
- (testCases.remove?.length || 0)
470
- }`;
471
-
472
- document.getElementById('loading').style.display = 'none';
473
- document.getElementById('content').style.display = 'block';
474
- document.getElementById('content').classList.add('fade-in');
475
-
476
- renderAllSections();
477
- updateCounts();
478
- } catch (error) {
479
- document.getElementById('loading').innerHTML =
480
- `<div class="status-message status-error">❌ Error loading test cases: ${error.message}</div>`;
481
- }
482
- }
483
-
484
- // Render functions
485
- function renderAllSections() {
486
- renderNewCases();
487
- renderModifyCases();
488
- renderRemoveCases();
489
- }
490
-
491
- function renderNewCases() {
492
- const container = document.getElementById('newCases');
493
- const cases = testCases.new || [];
494
-
495
- if (cases.length === 0) {
496
- container.innerHTML = '<div class="empty-section">No new test cases</div>';
497
- return;
498
- }
499
-
500
- container.innerHTML = cases.map((tc) => `
501
- <div class="test-case" id="new-${tc.id}">
502
- <div class="test-case-header">
503
- <span class="test-case-id">${tc.id}</span>
504
- <div class="test-case-actions">
505
- <button class="btn btn-edit" onclick="startEdit('new', '${tc.id}')">✏️ Edit</button>
506
- <button class="btn btn-delete" onclick="deleteTestCase('new', '${tc.id}')">🗑️ Delete</button>
507
- </div>
508
- </div>
509
- <div class="test-case-content">
510
- <div id="new-${tc.id}-display">
511
- <div class="description-text">${escapeHtml(tc.description)}</div>
512
- </div>
513
- <div id="new-${tc.id}-edit" style="display: none;">
514
- <div class="edit-form">
515
- <div class="form-group">
516
- <label class="form-label">Description:</label>
517
- <textarea class="form-control" id="new-${tc.id}-desc">${escapeHtml(tc.description)}</textarea>
518
- </div>
519
- <div class="test-case-actions">
520
- <button class="btn btn-save" onclick="saveEdit('new', '${tc.id}')">💾 Save</button>
521
- <button class="btn btn-cancel" onclick="cancelEdit('new', '${tc.id}')">❌ Cancel</button>
522
- </div>
523
- </div>
524
- </div>
525
- </div>
526
- </div>
527
- `).join('');
528
- }
529
-
530
- function renderModifyCases() {
531
- const container = document.getElementById('modifyCases');
532
- const cases = testCases.modify || [];
533
-
534
- if (cases.length === 0) {
535
- container.innerHTML = '<div class="empty-section">No modified test cases</div>';
536
- return;
537
- }
538
-
539
- container.innerHTML = cases.map((tc) => `
540
- <div class="test-case" id="modify-${tc.id}">
541
- <div class="test-case-header">
542
- <span class="test-case-id">${tc.id}</span>
543
- <div class="test-case-actions">
544
- <button class="btn btn-edit" onclick="startEdit('modify', '${tc.id}')">✏️ Edit</button>
545
- <button class="btn btn-delete" onclick="deleteTestCase('modify', '${tc.id}')">🗑️ Delete</button>
546
- </div>
547
- </div>
548
- <div class="test-case-content">
549
- <div id="modify-${tc.id}-display">
550
- <div class="original-text">
551
- <strong>Original:</strong><br>${escapeHtml(tc.original)}
552
- </div>
553
- <div class="modified-text">
554
- <strong>Modified:</strong><br>${escapeHtml(tc.modified)}
555
- </div>
556
- </div>
557
- <div id="modify-${tc.id}-edit" style="display: none;">
558
- <div class="edit-form">
559
- <div class="form-group">
560
- <label class="form-label">Original (Read-only):</label>
561
- <textarea class="form-control" readonly>${escapeHtml(tc.original)}</textarea>
562
- </div>
563
- <div class="form-group">
564
- <label class="form-label">Modified:</label>
565
- <textarea class="form-control" id="modify-${tc.id}-mod">${escapeHtml(tc.modified)}</textarea>
566
- </div>
567
- <div class="test-case-actions">
568
- <button class="btn btn-save" onclick="saveEdit('modify', '${tc.id}')">💾 Save</button>
569
- <button class="btn btn-cancel" onclick="cancelEdit('modify', '${tc.id}')">❌ Cancel</button>
570
- </div>
571
- </div>
572
- </div>
573
- </div>
574
- </div>
575
- `).join('');
576
- }
577
-
578
- function renderRemoveCases() {
579
- const container = document.getElementById('removeCases');
580
- const cases = testCases.remove || [];
581
-
582
- if (cases.length === 0) {
583
- container.innerHTML = '<div class="empty-section">No test cases to remove</div>';
584
- return;
585
- }
586
-
587
- container.innerHTML = cases.map((tc) => `
588
- <div class="test-case" id="remove-${tc.id}">
589
- <div class="test-case-header">
590
- <span class="test-case-id">${tc.id}</span>
591
- <div class="test-case-actions">
592
- <button class="btn btn-delete" onclick="deleteTestCase('remove', '${tc.id}')">🗑️ Remove</button>
593
- </div>
594
- </div>
595
- <div class="test-case-content">
596
- <div class="remove-text">
597
- <strong>⚠️ This test case will be removed:</strong><br>
598
- ${escapeHtml(tc.description)}
599
- </div>
600
- </div>
601
- </div>
602
- `).join('');
603
- }
604
-
605
- // Edit functions
606
- function startEdit(type, id) {
607
- const editKey = `${type}-${id}`;
608
- if (editingStates.has(editKey)) return;
609
-
610
- editingStates.add(editKey);
611
- document.getElementById(`${type}-${id}-display`).style.display = 'none';
612
- document.getElementById(`${type}-${id}-edit`).style.display = 'block';
613
- }
614
-
615
- function cancelEdit(type, id) {
616
- const editKey = `${type}-${id}`;
617
- editingStates.delete(editKey);
618
- document.getElementById(`${type}-${id}-display`).style.display = 'block';
619
- document.getElementById(`${type}-${id}-edit`).style.display = 'none';
620
-
621
- // Reset form values
622
- const testCase = findTestCase(type, id);
623
- if (testCase) {
624
- if (type === 'new') {
625
- document.getElementById(`new-${id}-desc`).value = testCase.description;
626
- } else if (type === 'modify') {
627
- document.getElementById(`modify-${id}-mod`).value = testCase.modified;
628
- }
629
- }
630
- }
631
-
632
- function findTestCase(type, id) {
633
- return testCases[type]?.find(tc => tc.id === id);
634
- }
635
-
636
- async function saveEdit(type, id) {
637
- try {
638
- const editKey = `${type}-${id}`;
639
- let updatedData = {};
640
-
641
- const testCase = findTestCase(type, id);
642
- if (!testCase) {
643
- showStatus('Test case not found', 'error');
644
- return;
645
- }
646
-
647
- if (type === 'new') {
648
- const newDesc = document.getElementById(`new-${id}-desc`).value.trim();
649
- if (!newDesc) {
650
- showStatus('Description cannot be empty', 'error');
651
- return;
652
- }
653
- updatedData = { description: newDesc };
654
- testCase.description = newDesc;
655
- } else if (type === 'modify') {
656
- const newMod = document.getElementById(`modify-${id}-mod`).value.trim();
657
- if (!newMod) {
658
- showStatus('Modified description cannot be empty', 'error');
659
- return;
660
- }
661
- updatedData = { modified: newMod };
662
- testCase.modified = newMod;
663
- }
664
-
665
- // Send update to server
666
- await apiCall(`/update/${sessionId}`, {
667
- method: 'POST',
668
- body: JSON.stringify({
669
- type,
670
- id: id,
671
- data: updatedData
672
- })
673
- });
674
-
675
- editingStates.delete(editKey);
676
- document.getElementById(`${type}-${id}-display`).style.display = 'block';
677
- document.getElementById(`${type}-${id}-edit`).style.display = 'none';
678
-
679
- // Re-render the specific section
680
- if (type === 'new') renderNewCases();
681
- else if (type === 'modify') renderModifyCases();
682
-
683
- showStatus('Changes saved successfully!', 'success');
684
- } catch (error) {
685
- showStatus(`Error saving changes: ${error.message}`, 'error');
686
- }
687
- }
688
-
689
- // Delete function
690
- async function deleteTestCase(type, id) {
691
- if (!confirm(`Are you sure you want to delete this test case?`)) return;
692
-
693
- try {
694
- showStatus('Deleting test case...', 'info');
695
-
696
- const result = await apiCall(`/delete/${sessionId}`, {
697
- method: 'POST',
698
- body: JSON.stringify({ type, id })
699
- });
700
-
701
- if (result.testCases) {
702
- testCases = result.testCases;
703
-
704
- // Ensure all arrays exist after update
705
- if (!testCases.new) testCases.new = [];
706
- if (!testCases.modify) testCases.modify = [];
707
- if (!testCases.remove) testCases.remove = [];
708
-
709
- renderAllSections();
710
- updateCounts();
711
- showStatus('Test case deleted successfully!', 'success');
712
- } else {
713
- showStatus('Unexpected response from server', 'error');
714
- }
715
- } catch (error) {
716
- showStatus(`Error deleting test case: ${error.message}`, 'error');
717
- }
718
- }
719
-
720
- // Approval functions
721
- async function approveTestCases() {
722
- if (!confirm('Are you sure you want to approve all test cases? This action cannot be undone.')) return;
723
-
724
- try {
725
- showStatus('Processing approval...', 'info');
726
-
727
- const result = await apiCall(`/approve/${sessionId}`, {
728
- method: 'POST',
729
- body: JSON.stringify(testCases)
730
- });
731
-
732
- showStatus('✅ Test cases approved successfully! You can close this window.', 'success');
733
-
734
- // Disable all controls
735
- document.querySelectorAll('button').forEach(btn => btn.disabled = true);
736
-
737
- setTimeout(() => {
738
- window.close();
739
- }, 2000);
740
- } catch (error) {
741
- showStatus(`Error approving test cases: ${error.message}`, 'error');
742
- }
743
- }
744
-
745
- async function cancelReview() {
746
- if (!confirm('Are you sure you want to cancel this review?')) return;
747
-
748
- try {
749
- showStatus('Cancelling review...', 'info');
750
-
751
- await apiCall(`/cancel/${sessionId}`, { method: 'POST' });
752
-
753
- showStatus('Review cancelled. You can close this window.', 'info');
754
-
755
- setTimeout(() => {
756
- window.close();
757
- }, 1500);
758
- } catch (error) {
759
- showStatus(`Error cancelling review: ${error.message}`, 'error');
760
- }
761
- }
762
-
763
- // Utility function
764
- function escapeHtml(text) {
765
- const div = document.createElement('div');
766
- div.textContent = text;
767
- return div.innerHTML;
768
- }
769
-
770
- // Initialize when page loads
771
- window.addEventListener('load', loadTestCases);
772
- </script>
773
- </body>
774
- </html>