@nbakka/mcp-appium 2.0.89 → 2.0.91

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.
@@ -3,26 +3,182 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Test Case Review</title>
6
+ <title>Test Case Review & Approval</title>
7
7
  <style>
8
- body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }
9
- .container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; }
10
- .section { margin-bottom: 30px; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
11
- .new { background-color: #e7f5e7; border-color: #4caf50; }
12
- .modify { background-color: #fff3cd; border-color: #ffc107; }
13
- .remove { background-color: #f8d7da; border-color: #dc3545; }
14
- .test-case { margin: 10px 0; padding: 10px; background: white; border-radius: 3px; }
15
- button { padding: 10px 20px; margin: 10px; border: none; border-radius: 5px; cursor: pointer; }
16
- .approve { background-color: #4caf50; color: white; }
17
- .cancel { background-color: #dc3545; color: white; }
18
- h2 { margin-top: 0; }
19
- .original { color: #666; text-decoration: line-through; }
20
- .modified { color: #000; font-weight: bold; }
8
+ body {
9
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
+ margin: 0;
11
+ padding: 20px;
12
+ background-color: #f5f5f5;
13
+ line-height: 1.6;
14
+ }
15
+ .container {
16
+ max-width: 1200px;
17
+ margin: 0 auto;
18
+ background: white;
19
+ padding: 30px;
20
+ border-radius: 12px;
21
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
22
+ }
23
+ .section {
24
+ margin-bottom: 30px;
25
+ padding: 25px;
26
+ border: 2px solid #ddd;
27
+ border-radius: 8px;
28
+ position: relative;
29
+ }
30
+ .section h2 {
31
+ margin-top: 0;
32
+ margin-bottom: 20px;
33
+ padding-bottom: 10px;
34
+ border-bottom: 2px solid;
35
+ }
36
+ .new {
37
+ background-color: #e8f5e8;
38
+ border-color: #4caf50;
39
+ }
40
+ .new h2 { border-bottom-color: #4caf50; color: #2e7d32; }
41
+ .modify {
42
+ background-color: #fff8e1;
43
+ border-color: #ff9800;
44
+ }
45
+ .modify h2 { border-bottom-color: #ff9800; color: #f57c00; }
46
+ .remove {
47
+ background-color: #ffebee;
48
+ border-color: #f44336;
49
+ }
50
+ .remove h2 { border-bottom-color: #f44336; color: #c62828; }
51
+ .test-case {
52
+ margin: 15px 0;
53
+ padding: 20px;
54
+ background: white;
55
+ border-radius: 6px;
56
+ border: 1px solid #e0e0e0;
57
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
58
+ }
59
+ .test-case-header {
60
+ display: flex;
61
+ justify-content: space-between;
62
+ align-items: center;
63
+ margin-bottom: 15px;
64
+ }
65
+ .test-case-id {
66
+ font-weight: bold;
67
+ color: #666;
68
+ font-size: 0.9em;
69
+ }
70
+ .edit-btn, .save-btn, .cancel-btn-inline, .delete-btn {
71
+ padding: 6px 12px;
72
+ border: none;
73
+ border-radius: 4px;
74
+ cursor: pointer;
75
+ font-size: 0.85em;
76
+ margin-left: 5px;
77
+ }
78
+ .edit-btn {
79
+ background-color: #2196f3;
80
+ color: white;
81
+ }
82
+ .save-btn {
83
+ background-color: #4caf50;
84
+ color: white;
85
+ }
86
+ .cancel-btn-inline {
87
+ background-color: #757575;
88
+ color: white;
89
+ }
90
+ .delete-btn {
91
+ background-color: #f44336;
92
+ color: white;
93
+ }
94
+ .delete-btn:hover {
95
+ background-color: #d32f2f;
96
+ }
97
+ .original-text {
98
+ color: #999;
99
+ text-decoration: line-through;
100
+ font-style: italic;
101
+ margin-bottom: 10px;
102
+ }
103
+ .modified-text {
104
+ color: #000;
105
+ font-weight: 500;
106
+ }
107
+ .editable-area {
108
+ width: 100%;
109
+ min-height: 60px;
110
+ padding: 10px;
111
+ border: 2px solid #ddd;
112
+ border-radius: 4px;
113
+ font-family: inherit;
114
+ font-size: 14px;
115
+ resize: vertical;
116
+ }
117
+ .editable-area:focus {
118
+ outline: none;
119
+ border-color: #2196f3;
120
+ }
121
+ .button-container {
122
+ text-align: center;
123
+ margin-top: 40px;
124
+ padding-top: 30px;
125
+ border-top: 2px solid #eee;
126
+ }
127
+ .main-button {
128
+ padding: 15px 30px;
129
+ margin: 0 15px;
130
+ border: none;
131
+ border-radius: 8px;
132
+ cursor: pointer;
133
+ font-size: 16px;
134
+ font-weight: 600;
135
+ text-transform: uppercase;
136
+ letter-spacing: 0.5px;
137
+ transition: all 0.3s ease;
138
+ }
139
+ .approve {
140
+ background-color: #4caf50;
141
+ color: white;
142
+ }
143
+ .approve:hover {
144
+ background-color: #45a049;
145
+ transform: translateY(-2px);
146
+ }
147
+ .cancel {
148
+ background-color: #f44336;
149
+ color: white;
150
+ }
151
+ .cancel:hover {
152
+ background-color: #da190b;
153
+ transform: translateY(-2px);
154
+ }
155
+ .status {
156
+ margin-top: 20px;
157
+ padding: 15px;
158
+ border-radius: 6px;
159
+ text-align: center;
160
+ font-weight: 500;
161
+ }
162
+ .status.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
163
+ .status.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
164
+ .status.info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
165
+ #loading {
166
+ text-align: center;
167
+ padding: 50px;
168
+ font-size: 18px;
169
+ color: #666;
170
+ }
171
+ .empty-section {
172
+ text-align: center;
173
+ color: #999;
174
+ font-style: italic;
175
+ padding: 30px;
176
+ }
21
177
  </style>
22
178
  </head>
23
179
  <body>
24
180
  <div class="container">
25
- <h1>Test Case Review</h1>
181
+ <h1>Test Case Review & Approval</h1>
26
182
  <div id="loading">Loading test cases...</div>
27
183
  <div id="content" style="display: none;">
28
184
  <div id="new-section" class="section new">
@@ -37,9 +193,10 @@
37
193
  <h2>Test Cases to Remove</h2>
38
194
  <div id="remove-cases"></div>
39
195
  </div>
40
- <div style="text-align: center; margin-top: 30px;">
41
- <button class="approve" onclick="approve()">Approve All</button>
42
- <button class="cancel" onclick="cancel()">Cancel</button>
196
+ <div class="button-container">
197
+ <button class="main-button approve" onclick="approveTestCases()">✓ Approve Test Cases</button>
198
+ <button class="main-button cancel" onclick="cancelReview()">✗ Cancel Review</button>
199
+ <div id="status" class="status" style="display: none;"></div>
43
200
  </div>
44
201
  </div>
45
202
  </div>
@@ -47,10 +204,14 @@
47
204
  <script>
48
205
  let sessionId;
49
206
  let testCases;
207
+ let editingStates = {};
50
208
 
51
209
  async function loadTestCases() {
52
210
  try {
53
211
  const response = await fetch('/api/testcases');
212
+ if (!response.ok) {
213
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
214
+ }
54
215
  const data = await response.json();
55
216
  sessionId = data.sessionId;
56
217
  testCases = data.testCases;
@@ -60,71 +221,218 @@
60
221
 
61
222
  renderTestCases();
62
223
  } catch (error) {
63
- document.getElementById('loading').innerHTML = 'Error loading test cases: ' + error.message;
224
+ document.getElementById('loading').innerHTML = `<div class="status error">Error loading test cases: ${error.message}</div>`;
64
225
  }
65
226
  }
66
227
 
67
228
  function renderTestCases() {
68
- // Render new test cases
69
- const newCases = document.getElementById('new-cases');
229
+ renderNewCases();
230
+ renderModifyCases();
231
+ renderRemoveCases();
232
+ }
233
+
234
+ function renderNewCases() {
235
+ const container = document.getElementById('new-cases');
70
236
  if (testCases.new && testCases.new.length > 0) {
71
- newCases.innerHTML = testCases.new.map(tc =>
72
- `<div class="test-case"><strong>ID:</strong> ${tc.id}<br><strong>Description:</strong> ${tc.description}</div>`
73
- ).join('');
237
+ container.innerHTML = testCases.new.map((tc, index) => `
238
+ <div class="test-case">
239
+ <div class="test-case-header">
240
+ <span class="test-case-id">ID: ${tc.id}</span>
241
+ <button class="edit-btn" onclick="toggleEdit('new', ${index})">Edit</button>
242
+ <button class="delete-btn" onclick="deleteTestCase('new', ${index})">Delete</button>
243
+ </div>
244
+ <div id="new-${index}-display" style="display: block;">
245
+ <strong>Description:</strong> ${tc.description}
246
+ </div>
247
+ <div id="new-${index}-edit" style="display: none;">
248
+ <label><strong>Description:</strong></label>
249
+ <textarea class="editable-area" id="new-${index}-desc">${tc.description}</textarea>
250
+ <div style="margin-top: 10px;">
251
+ <button class="save-btn" onclick="saveEdit('new', ${index})">Save</button>
252
+ <button class="cancel-btn-inline" onclick="cancelEdit('new', ${index})">Cancel</button>
253
+ </div>
254
+ </div>
255
+ </div>
256
+ `).join('');
74
257
  } else {
75
- newCases.innerHTML = '<p>No new test cases</p>';
258
+ container.innerHTML = '<div class="empty-section">No new test cases</div>';
76
259
  }
260
+ }
77
261
 
78
- // Render modified test cases
79
- const modifyCases = document.getElementById('modify-cases');
262
+ function renderModifyCases() {
263
+ const container = document.getElementById('modify-cases');
80
264
  if (testCases.modify && testCases.modify.length > 0) {
81
- modifyCases.innerHTML = testCases.modify.map(tc =>
82
- `<div class="test-case">
83
- <strong>ID:</strong> ${tc.id}<br>
84
- <strong>Original:</strong> <span class="original">${tc.original}</span><br>
85
- <strong>Modified:</strong> <span class="modified">${tc.modified}</span>
86
- </div>`
87
- ).join('');
265
+ container.innerHTML = testCases.modify.map((tc, index) => `
266
+ <div class="test-case">
267
+ <div class="test-case-header">
268
+ <span class="test-case-id">ID: ${tc.id}</span>
269
+ <button class="edit-btn" onclick="toggleEdit('modify', ${index})">Edit</button>
270
+ <button class="delete-btn" onclick="deleteTestCase('modify', ${index})">Delete</button>
271
+ </div>
272
+ <div id="modify-${index}-display" style="display: block;">
273
+ <div class="original-text"><strong>Original:</strong> ${tc.original}</div>
274
+ <div class="modified-text"><strong>Modified:</strong> ${tc.modified}</div>
275
+ </div>
276
+ <div id="modify-${index}-edit" style="display: none;">
277
+ <label><strong>Original:</strong></label>
278
+ <textarea class="editable-area" id="modify-${index}-orig" readonly style="background-color: #f5f5f5;">${tc.original}</textarea>
279
+ <label style="margin-top: 10px; display: block;"><strong>Modified:</strong></label>
280
+ <textarea class="editable-area" id="modify-${index}-mod">${tc.modified}</textarea>
281
+ <div style="margin-top: 10px;">
282
+ <button class="save-btn" onclick="saveEdit('modify', ${index})">Save</button>
283
+ <button class="cancel-btn-inline" onclick="cancelEdit('modify', ${index})">Cancel</button>
284
+ </div>
285
+ </div>
286
+ </div>
287
+ `).join('');
88
288
  } else {
89
- modifyCases.innerHTML = '<p>No modified test cases</p>';
289
+ container.innerHTML = '<div class="empty-section">No modified test cases</div>';
90
290
  }
291
+ }
91
292
 
92
- // Render remove test cases
93
- const removeCases = document.getElementById('remove-cases');
293
+ function renderRemoveCases() {
294
+ const container = document.getElementById('remove-cases');
94
295
  if (testCases.remove && testCases.remove.length > 0) {
95
- removeCases.innerHTML = testCases.remove.map(tc =>
96
- `<div class="test-case"><strong>ID:</strong> ${tc.id}<br><strong>Description:</strong> ${tc.description}</div>`
97
- ).join('');
296
+ container.innerHTML = testCases.remove.map((tc, index) => `
297
+ <div class="test-case">
298
+ <div class="test-case-header">
299
+ <span class="test-case-id">ID: ${tc.id}</span>
300
+ </div>
301
+ <div>
302
+ <strong>Description:</strong> ${tc.description}
303
+ <div style="margin-top: 10px; color: #c62828; font-weight: 500;">
304
+ ⚠️ This test case will be removed
305
+ </div>
306
+ </div>
307
+ </div>
308
+ `).join('');
98
309
  } else {
99
- removeCases.innerHTML = '<p>No test cases to remove</p>';
310
+ container.innerHTML = '<div class="empty-section">No test cases to remove</div>';
100
311
  }
101
312
  }
102
313
 
103
- async function approve() {
314
+ function toggleEdit(type, index) {
315
+ const displayDiv = document.getElementById(`${type}-${index}-display`);
316
+ const editDiv = document.getElementById(`${type}-${index}-edit`);
317
+
318
+ if (displayDiv.style.display === 'block') {
319
+ displayDiv.style.display = 'none';
320
+ editDiv.style.display = 'block';
321
+ editingStates[`${type}-${index}`] = true;
322
+ }
323
+ }
324
+
325
+ function saveEdit(type, index) {
326
+ if (type === 'new') {
327
+ const newDesc = document.getElementById(`new-${index}-desc`).value.trim();
328
+ if (newDesc) {
329
+ testCases.new[index].description = newDesc;
330
+ }
331
+ } else if (type === 'modify') {
332
+ const newMod = document.getElementById(`modify-${index}-mod`).value.trim();
333
+ if (newMod) {
334
+ testCases.modify[index].modified = newMod;
335
+ }
336
+ }
337
+
338
+ cancelEdit(type, index);
339
+ renderTestCases();
340
+ showStatus('Changes saved successfully!', 'success');
341
+ }
342
+
343
+ function cancelEdit(type, index) {
344
+ const displayDiv = document.getElementById(`${type}-${index}-display`);
345
+ const editDiv = document.getElementById(`${type}-${index}-edit`);
346
+
347
+ displayDiv.style.display = 'block';
348
+ editDiv.style.display = 'none';
349
+ delete editingStates[`${type}-${index}`];
350
+ }
351
+
352
+ function showStatus(message, type) {
353
+ const statusDiv = document.getElementById('status');
354
+ statusDiv.textContent = message;
355
+ statusDiv.className = `status ${type}`;
356
+ statusDiv.style.display = 'block';
357
+
358
+ setTimeout(() => {
359
+ statusDiv.style.display = 'none';
360
+ }, 3000);
361
+ }
362
+
363
+ async function approveTestCases() {
104
364
  try {
365
+ showStatus('Processing approval...', 'info');
105
366
  const response = await fetch(`/approve/${sessionId}`, {
106
367
  method: 'POST',
107
368
  headers: { 'Content-Type': 'application/json' },
108
369
  body: JSON.stringify(testCases)
109
370
  });
371
+
372
+ if (!response.ok) {
373
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
374
+ }
375
+
110
376
  const result = await response.json();
111
- alert(result.message);
112
- window.close();
377
+ showStatus(result.message || 'Test cases approved successfully!', 'success');
378
+
379
+ setTimeout(() => {
380
+ window.close();
381
+ }, 2000);
113
382
  } catch (error) {
114
- alert('Error approving test cases: ' + error.message);
383
+ showStatus(`Error approving test cases: ${error.message}`, 'error');
115
384
  }
116
385
  }
117
386
 
118
- async function cancel() {
387
+ async function cancelReview() {
119
388
  try {
389
+ showStatus('Cancelling review...', 'info');
120
390
  const response = await fetch(`/cancel/${sessionId}`, {
121
391
  method: 'POST'
122
392
  });
393
+
394
+ if (!response.ok) {
395
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
396
+ }
397
+
123
398
  const result = await response.json();
124
- alert(result.message);
125
- window.close();
399
+ showStatus(result.message || 'Review cancelled', 'info');
400
+
401
+ setTimeout(() => {
402
+ window.close();
403
+ }, 2000);
404
+ } catch (error) {
405
+ showStatus(`Error cancelling review: ${error.message}`, 'error');
406
+ }
407
+ }
408
+
409
+ async function deleteTestCase(type, index) {
410
+ const confirmation = confirm("Are you sure you want to delete this test case?");
411
+ if (!confirmation) return;
412
+
413
+ try {
414
+ showStatus('Deleting test case...', 'info');
415
+ const response = await fetch(`/delete/${sessionId}`, {
416
+ method: 'POST',
417
+ headers: { 'Content-Type': 'application/json' },
418
+ body: JSON.stringify({ type, index })
419
+ });
420
+
421
+ if (!response.ok) {
422
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
423
+ }
424
+
425
+ // Remove the deleted test case from the local state
426
+ if (type === 'new') {
427
+ testCases.new.splice(index, 1);
428
+ } else if (type === 'modify') {
429
+ testCases.modify.splice(index, 1);
430
+ }
431
+
432
+ renderTestCases();
433
+ showStatus('Test case deleted successfully!', 'success');
126
434
  } catch (error) {
127
- alert('Error cancelling review: ' + error.message);
435
+ showStatus(`Error deleting test case: ${error.message}`, 'error');
128
436
  }
129
437
  }
130
438
 
package/lib/server.js CHANGED
@@ -809,48 +809,44 @@ tool(
809
809
  const path = require('path');
810
810
  const sessionId = Date.now().toString();
811
811
 
812
- // Parse test cases into categories
813
- const parsedTestCases = {
814
- new: [],
815
- modify: [],
816
- remove: []
817
- };
812
+ // Add the improved parseTestCases function here
813
+ function parseTestCases(testCasesArray) {
814
+ const parsed = {
815
+ new: [],
816
+ modify: [],
817
+ remove: []
818
+ };
818
819
 
819
- testCases.forEach((tc, index) => {
820
- if (!tc || !Array.isArray(tc) || tc.length < 2) {
821
- console.log(`Skipping invalid test case at index ${index}:`, tc);
822
- return;
823
- }
820
+ testCasesArray.forEach(tc => {
821
+ if (tc.length === 4 && tc[2] === 'Modify') {
822
+ // Modify: [original, modified, "Modify", tcmsId]
823
+ parsed.modify.push({
824
+ id: tc[3],
825
+ original: tc[0],
826
+ modified: tc[1]
827
+ });
828
+ } else if (tc.length === 3 && tc[1] === 'Remove') {
829
+ // Remove: [description, "Remove", tcmsId]
830
+ parsed.remove.push({
831
+ id: tc[2],
832
+ description: tc[0]
833
+ });
834
+ } else if (tc.length === 2 && tc[1] === 'New') {
835
+ // New: [description, "New"]
836
+ parsed.new.push({
837
+ description: tc[0],
838
+ id: `NEW-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
839
+ });
840
+ }
841
+ });
824
842
 
825
- const lastElement = tc[tc.length - 1];
826
- const secondLastElement = tc.length > 1 ? tc[tc.length - 2] : null;
827
-
828
- if (lastElement?.toLowerCase() === 'new') {
829
- // Format: [description, "New"]
830
- parsedTestCases.new.push({
831
- description: tc[0],
832
- id: `NEW-${index + 1}`
833
- });
834
- } else if (secondLastElement?.toLowerCase() === 'modify' && tc.length === 4) {
835
- // Format: [originalDescription, newDescription, "Modify", testCaseId]
836
- parsedTestCases.modify.push({
837
- id: tc[3],
838
- original: tc[0],
839
- modified: tc[1]
840
- });
841
- } else if (secondLastElement?.toLowerCase() === 'remove' && tc.length === 3) {
842
- // Format: [description, "Remove", testCaseId]
843
- parsedTestCases.remove.push({
844
- id: tc[2],
845
- description: tc[0]
846
- });
847
- } else {
848
- console.log(`Unrecognized test case format at index ${index}:`, tc);
849
- }
850
- });
843
+ return parsed;
844
+ }
851
845
 
852
- console.log('Parsed test cases:', JSON.stringify(parsedTestCases, null, 2));
846
+ // Use the improved parsing function
847
+ const parsedTestCases = parseTestCases(testCases);
853
848
 
849
+ // Remove the old parsing logic and console.log statements that cause JSON errors
854
850
  // Initialize session state
855
851
  approvalSessions.set(sessionId, {
856
852
  status: 'pending',
@@ -900,13 +896,37 @@ tool(
900
896
  res.json({ status: 'cancelled', message: 'Review cancelled' });
901
897
  });
902
898
 
899
+ app.post(`/delete/${sessionId}`, (req, res) => {
900
+ const session = approvalSessions.get(sessionId);
901
+ if (!session) {
902
+ return res.status(404).json({ error: 'Session not found' });
903
+ }
904
+
905
+ const { type, index } = req.body;
906
+
907
+ try {
908
+ if (type === 'new' && session.testCases.new && index < session.testCases.new.length) {
909
+ session.testCases.new.splice(index, 1);
910
+ } else if (type === 'modify' && session.testCases.modify && index < session.testCases.modify.length) {
911
+ session.testCases.modify.splice(index, 1);
912
+ } else {
913
+ return res.status(400).json({ error: 'Invalid type or index' });
914
+ }
915
+
916
+ approvalSessions.set(sessionId, session);
917
+ res.json({ status: 'deleted', message: 'Test case deleted successfully' });
918
+ } catch (error) {
919
+ res.status(500).json({ error: 'Failed to delete test case' });
920
+ }
921
+ });
922
+
903
923
  const server = app.listen(port, async () => {
904
- console.log(`Test case review server started on http://localhost:${port}`);
924
+ // Remove console.log to prevent JSON parsing errors
905
925
  try {
906
926
  const { default: open } = await import('open');
907
927
  await open(`http://localhost:${port}`);
908
928
  } catch (openError) {
909
- console.log('Failed to open browser automatically:', openError.message);
929
+ // Silent fail for browser opening
910
930
  }
911
931
  });
912
932
 
@@ -925,23 +945,21 @@ tool(
925
945
  }
926
946
  }, 300000);
927
947
 
928
- const response = {
948
+ // Return clean JSON response
949
+ return JSON.stringify({
929
950
  status: "review_started",
930
951
  sessionId: sessionId,
931
952
  message: "Test case review interface opened in browser. Use check_approval_status tool to poll for approval.",
932
953
  testCasesCount: testCases.length,
933
954
  browserUrl: `http://localhost:${port}`,
934
955
  instructions: "Poll every 25 seconds using check_approval_status tool until approved or timeout (5 minutes)"
935
- };
936
-
937
- return JSON.stringify(response);
956
+ });
938
957
 
939
958
  } catch (err) {
940
- const errorResponse = {
959
+ return JSON.stringify({
941
960
  status: "error",
942
961
  message: `Error setting up review interface: ${err.message}`
943
- };
944
- return JSON.stringify(errorResponse);
962
+ });
945
963
  }
946
964
  }
947
965
  );
@@ -957,10 +975,10 @@ tool(
957
975
 
958
976
  const session = approvalSessions.get(sessionId);
959
977
  if (!session) {
960
- return JSON.stringify({
978
+ return {
961
979
  status: "error",
962
980
  message: `Session not found. Invalid session ID: ${sessionId}`
963
- });
981
+ };
964
982
  }
965
983
 
966
984
  const currentTime = Date.now();
@@ -974,41 +992,41 @@ tool(
974
992
  }
975
993
  approvalSessions.delete(sessionId);
976
994
 
977
- return JSON.stringify({
995
+ return {
978
996
  status: "approved",
979
997
  message: "Test cases approved successfully!",
980
998
  elapsedTime: elapsedTime,
981
999
  approvedTestCases: approvedTestCases
982
- });
1000
+ };
983
1001
  } else if (session.status === 'cancelled') {
984
1002
  if (session.server) {
985
1003
  session.server.close();
986
1004
  }
987
1005
  approvalSessions.delete(sessionId);
988
1006
 
989
- return JSON.stringify({
1007
+ return {
990
1008
  status: "cancelled",
991
1009
  message: "Review was cancelled by the user",
992
1010
  elapsedTime: elapsedTime
993
- });
1011
+ };
994
1012
  } else if (session.status === 'timeout' || elapsedTime > 300) {
995
1013
  if (session.server) {
996
1014
  session.server.close();
997
1015
  }
998
1016
  approvalSessions.delete(sessionId);
999
1017
 
1000
- return JSON.stringify({
1018
+ return {
1001
1019
  status: "timeout",
1002
1020
  message: "Review session timed out",
1003
1021
  elapsedTime: elapsedTime
1004
- });
1022
+ };
1005
1023
  } else {
1006
- return JSON.stringify({
1024
+ return {
1007
1025
  status: "pending",
1008
1026
  message: "Test cases are still pending approval",
1009
1027
  elapsedTime: elapsedTime,
1010
1028
  remainingTime: Math.max(0, 300 - elapsedTime)
1011
- });
1029
+ };
1012
1030
  }
1013
1031
  }
1014
1032
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbakka/mcp-appium",
3
- "version": "2.0.89",
3
+ "version": "2.0.91",
4
4
  "description": "Appium MCP",
5
5
  "engines": {
6
6
  "node": ">=18"