@nbakka/mcp-appium 2.0.88 → 2.0.90

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,38 +1,404 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html lang="en">
3
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4
6
  <title>Test Case Review & Approval</title>
5
- <link rel="stylesheet" href="/styles.css">
7
+ <style>
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 {
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
+ .original-text {
91
+ color: #999;
92
+ text-decoration: line-through;
93
+ font-style: italic;
94
+ margin-bottom: 10px;
95
+ }
96
+ .modified-text {
97
+ color: #000;
98
+ font-weight: 500;
99
+ }
100
+ .editable-area {
101
+ width: 100%;
102
+ min-height: 60px;
103
+ padding: 10px;
104
+ border: 2px solid #ddd;
105
+ border-radius: 4px;
106
+ font-family: inherit;
107
+ font-size: 14px;
108
+ resize: vertical;
109
+ }
110
+ .editable-area:focus {
111
+ outline: none;
112
+ border-color: #2196f3;
113
+ }
114
+ .button-container {
115
+ text-align: center;
116
+ margin-top: 40px;
117
+ padding-top: 30px;
118
+ border-top: 2px solid #eee;
119
+ }
120
+ .main-button {
121
+ padding: 15px 30px;
122
+ margin: 0 15px;
123
+ border: none;
124
+ border-radius: 8px;
125
+ cursor: pointer;
126
+ font-size: 16px;
127
+ font-weight: 600;
128
+ text-transform: uppercase;
129
+ letter-spacing: 0.5px;
130
+ transition: all 0.3s ease;
131
+ }
132
+ .approve {
133
+ background-color: #4caf50;
134
+ color: white;
135
+ }
136
+ .approve:hover {
137
+ background-color: #45a049;
138
+ transform: translateY(-2px);
139
+ }
140
+ .cancel {
141
+ background-color: #f44336;
142
+ color: white;
143
+ }
144
+ .cancel:hover {
145
+ background-color: #da190b;
146
+ transform: translateY(-2px);
147
+ }
148
+ .status {
149
+ margin-top: 20px;
150
+ padding: 15px;
151
+ border-radius: 6px;
152
+ text-align: center;
153
+ font-weight: 500;
154
+ }
155
+ .status.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
156
+ .status.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
157
+ .status.info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
158
+ #loading {
159
+ text-align: center;
160
+ padding: 50px;
161
+ font-size: 18px;
162
+ color: #666;
163
+ }
164
+ .empty-section {
165
+ text-align: center;
166
+ color: #999;
167
+ font-style: italic;
168
+ padding: 30px;
169
+ }
170
+ </style>
6
171
  </head>
7
172
  <body>
8
173
  <div class="container">
9
- <h1>Review Test Cases</h1>
10
-
11
- <!-- New Test Cases Section -->
12
- <div class="section" id="new-testcases-section">
13
- <h2>New Test Cases</h2>
14
- <div id="new-testcases"></div>
174
+ <h1>Test Case Review & Approval</h1>
175
+ <div id="loading">Loading test cases...</div>
176
+ <div id="content" style="display: none;">
177
+ <div id="new-section" class="section new">
178
+ <h2>New Test Cases</h2>
179
+ <div id="new-cases"></div>
180
+ </div>
181
+ <div id="modify-section" class="section modify">
182
+ <h2>Modified Test Cases</h2>
183
+ <div id="modify-cases"></div>
184
+ </div>
185
+ <div id="remove-section" class="section remove">
186
+ <h2>Test Cases to Remove</h2>
187
+ <div id="remove-cases"></div>
188
+ </div>
189
+ <div class="button-container">
190
+ <button class="main-button approve" onclick="approveTestCases()">✓ Approve Test Cases</button>
191
+ <button class="main-button cancel" onclick="cancelReview()">✗ Cancel Review</button>
192
+ <div id="status" class="status" style="display: none;"></div>
193
+ </div>
15
194
  </div>
195
+ </div>
16
196
 
17
- <!-- Modified Test Cases Section -->
18
- <div class="section" id="modified-testcases-section">
19
- <h2>Modified Test Cases</h2>
20
- <div id="modified-testcases"></div>
21
- </div>
197
+ <script>
198
+ let sessionId;
199
+ let testCases;
200
+ let editingStates = {};
22
201
 
23
- <!-- Test Cases to Remove Section -->
24
- <div class="section" id="remove-testcases-section">
25
- <h2>Test Cases to Remove</h2>
26
- <div id="remove-testcases"></div>
27
- </div>
202
+ async function loadTestCases() {
203
+ try {
204
+ const response = await fetch('/api/testcases');
205
+ if (!response.ok) {
206
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
207
+ }
208
+ const data = await response.json();
209
+ sessionId = data.sessionId;
210
+ testCases = data.testCases;
28
211
 
29
- <div class="button-container">
30
- <button id="approve-btn" onclick="approveTestCases()">✓ Approve Test Cases</button>
31
- <button id="cancel-btn" onclick="cancelReview()">✗ Cancel Review</button>
32
- <div id="status" class="status"></div>
33
- </div>
34
- </div>
212
+ document.getElementById('loading').style.display = 'none';
213
+ document.getElementById('content').style.display = 'block';
214
+
215
+ renderTestCases();
216
+ } catch (error) {
217
+ document.getElementById('loading').innerHTML = `<div class="status error">Error loading test cases: ${error.message}</div>`;
218
+ }
219
+ }
220
+
221
+ function renderTestCases() {
222
+ renderNewCases();
223
+ renderModifyCases();
224
+ renderRemoveCases();
225
+ }
226
+
227
+ function renderNewCases() {
228
+ const container = document.getElementById('new-cases');
229
+ if (testCases.new && testCases.new.length > 0) {
230
+ container.innerHTML = testCases.new.map((tc, index) => `
231
+ <div class="test-case">
232
+ <div class="test-case-header">
233
+ <span class="test-case-id">ID: ${tc.id}</span>
234
+ <button class="edit-btn" onclick="toggleEdit('new', ${index})">Edit</button>
235
+ </div>
236
+ <div id="new-${index}-display" style="display: block;">
237
+ <strong>Description:</strong> ${tc.description}
238
+ </div>
239
+ <div id="new-${index}-edit" style="display: none;">
240
+ <label><strong>Description:</strong></label>
241
+ <textarea class="editable-area" id="new-${index}-desc">${tc.description}</textarea>
242
+ <div style="margin-top: 10px;">
243
+ <button class="save-btn" onclick="saveEdit('new', ${index})">Save</button>
244
+ <button class="cancel-btn-inline" onclick="cancelEdit('new', ${index})">Cancel</button>
245
+ </div>
246
+ </div>
247
+ </div>
248
+ `).join('');
249
+ } else {
250
+ container.innerHTML = '<div class="empty-section">No new test cases</div>';
251
+ }
252
+ }
253
+
254
+ function renderModifyCases() {
255
+ const container = document.getElementById('modify-cases');
256
+ if (testCases.modify && testCases.modify.length > 0) {
257
+ container.innerHTML = testCases.modify.map((tc, index) => `
258
+ <div class="test-case">
259
+ <div class="test-case-header">
260
+ <span class="test-case-id">ID: ${tc.id}</span>
261
+ <button class="edit-btn" onclick="toggleEdit('modify', ${index})">Edit</button>
262
+ </div>
263
+ <div id="modify-${index}-display" style="display: block;">
264
+ <div class="original-text"><strong>Original:</strong> ${tc.original}</div>
265
+ <div class="modified-text"><strong>Modified:</strong> ${tc.modified}</div>
266
+ </div>
267
+ <div id="modify-${index}-edit" style="display: none;">
268
+ <label><strong>Original:</strong></label>
269
+ <textarea class="editable-area" id="modify-${index}-orig" readonly style="background-color: #f5f5f5;">${tc.original}</textarea>
270
+ <label style="margin-top: 10px; display: block;"><strong>Modified:</strong></label>
271
+ <textarea class="editable-area" id="modify-${index}-mod">${tc.modified}</textarea>
272
+ <div style="margin-top: 10px;">
273
+ <button class="save-btn" onclick="saveEdit('modify', ${index})">Save</button>
274
+ <button class="cancel-btn-inline" onclick="cancelEdit('modify', ${index})">Cancel</button>
275
+ </div>
276
+ </div>
277
+ </div>
278
+ `).join('');
279
+ } else {
280
+ container.innerHTML = '<div class="empty-section">No modified test cases</div>';
281
+ }
282
+ }
283
+
284
+ function renderRemoveCases() {
285
+ const container = document.getElementById('remove-cases');
286
+ if (testCases.remove && testCases.remove.length > 0) {
287
+ container.innerHTML = testCases.remove.map((tc, index) => `
288
+ <div class="test-case">
289
+ <div class="test-case-header">
290
+ <span class="test-case-id">ID: ${tc.id}</span>
291
+ </div>
292
+ <div>
293
+ <strong>Description:</strong> ${tc.description}
294
+ <div style="margin-top: 10px; color: #c62828; font-weight: 500;">
295
+ ⚠️ This test case will be removed
296
+ </div>
297
+ </div>
298
+ </div>
299
+ `).join('');
300
+ } else {
301
+ container.innerHTML = '<div class="empty-section">No test cases to remove</div>';
302
+ }
303
+ }
304
+
305
+ function toggleEdit(type, index) {
306
+ const displayDiv = document.getElementById(`${type}-${index}-display`);
307
+ const editDiv = document.getElementById(`${type}-${index}-edit`);
308
+
309
+ if (displayDiv.style.display === 'block') {
310
+ displayDiv.style.display = 'none';
311
+ editDiv.style.display = 'block';
312
+ editingStates[`${type}-${index}`] = true;
313
+ }
314
+ }
315
+
316
+ function saveEdit(type, index) {
317
+ if (type === 'new') {
318
+ const newDesc = document.getElementById(`new-${index}-desc`).value.trim();
319
+ if (newDesc) {
320
+ testCases.new[index].description = newDesc;
321
+ }
322
+ } else if (type === 'modify') {
323
+ const newMod = document.getElementById(`modify-${index}-mod`).value.trim();
324
+ if (newMod) {
325
+ testCases.modify[index].modified = newMod;
326
+ }
327
+ }
328
+
329
+ cancelEdit(type, index);
330
+ renderTestCases();
331
+ showStatus('Changes saved successfully!', 'success');
332
+ }
333
+
334
+ function cancelEdit(type, index) {
335
+ const displayDiv = document.getElementById(`${type}-${index}-display`);
336
+ const editDiv = document.getElementById(`${type}-${index}-edit`);
337
+
338
+ displayDiv.style.display = 'block';
339
+ editDiv.style.display = 'none';
340
+ delete editingStates[`${type}-${index}`];
341
+ }
342
+
343
+ function showStatus(message, type) {
344
+ const statusDiv = document.getElementById('status');
345
+ statusDiv.textContent = message;
346
+ statusDiv.className = `status ${type}`;
347
+ statusDiv.style.display = 'block';
348
+
349
+ setTimeout(() => {
350
+ statusDiv.style.display = 'none';
351
+ }, 3000);
352
+ }
353
+
354
+ async function approveTestCases() {
355
+ try {
356
+ showStatus('Processing approval...', 'info');
357
+ const response = await fetch(`/approve/${sessionId}`, {
358
+ method: 'POST',
359
+ headers: { 'Content-Type': 'application/json' },
360
+ body: JSON.stringify(testCases)
361
+ });
362
+
363
+ if (!response.ok) {
364
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
365
+ }
366
+
367
+ const result = await response.json();
368
+ showStatus(result.message || 'Test cases approved successfully!', 'success');
369
+
370
+ setTimeout(() => {
371
+ window.close();
372
+ }, 2000);
373
+ } catch (error) {
374
+ showStatus(`Error approving test cases: ${error.message}`, 'error');
375
+ }
376
+ }
377
+
378
+ async function cancelReview() {
379
+ try {
380
+ showStatus('Cancelling review...', 'info');
381
+ const response = await fetch(`/cancel/${sessionId}`, {
382
+ method: 'POST'
383
+ });
384
+
385
+ if (!response.ok) {
386
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
387
+ }
388
+
389
+ const result = await response.json();
390
+ showStatus(result.message || 'Review cancelled', 'info');
391
+
392
+ setTimeout(() => {
393
+ window.close();
394
+ }, 2000);
395
+ } catch (error) {
396
+ showStatus(`Error cancelling review: ${error.message}`, 'error');
397
+ }
398
+ }
35
399
 
36
- <script src="/script.js"></script>
400
+ // Load test cases when page loads
401
+ window.onload = loadTestCases;
402
+ </script>
37
403
  </body>
38
404
  </html>
package/lib/server.js CHANGED
@@ -754,7 +754,7 @@ ${guidelines}`
754
754
  await fs.writeFile(testCasesFilePath, `Error generating test cases: ${error.message}`);
755
755
  });
756
756
 
757
- return "✅ Test case generation started. Use 'check_testcases_status' tool to check if generation is complete.";
757
+ return "✅ Test case generation started. Use 'check_testcases_status' tool to check if generation is complete. Try max 10 times";
758
758
  } catch (err) {
759
759
  return `❌ Error starting test case generation: ${err.message}`;
760
760
  }
@@ -768,7 +768,7 @@ tool(
768
768
  async () => {
769
769
  try {
770
770
  // Wait for 20 seconds before checking
771
- await new Promise(resolve => setTimeout(resolve, 20000));
771
+ await new Promise(resolve => setTimeout(resolve, 25000));
772
772
 
773
773
  const testCasesFilePath = path.join(__dirname, 'generated-testcases.txt');
774
774
 
@@ -809,49 +809,49 @@ 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
- };
818
-
819
- testCases.forEach(tc => {
820
- if (!tc || tc.length < 2) return; // Skip invalid entries
821
-
822
- if (tc.length === 2 && tc[1].toLowerCase() === 'new') {
823
- // Format: [description, "New"]
824
- parsedTestCases.new.push(tc[0]);
825
- } else if (tc.length >= 3) {
826
- const type = tc[2]?.toLowerCase();
827
- if (type === 'modify' && tc.length >= 4) {
828
- // Format: [originalDescription, newDescription, "Modify", testCaseId]
829
- parsedTestCases.modify.push({
830
- id: tc[3] || 'N/A',
831
- original: tc[0], // Original description
832
- modified: tc[1] // New/modified description
812
+ // Add the improved parseTestCases function here
813
+ function parseTestCases(testCasesArray) {
814
+ const parsed = {
815
+ new: [],
816
+ modify: [],
817
+ remove: []
818
+ };
819
+
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]
833
827
  });
834
- } else if (type === 'remove') {
835
- // Format: [description, "Remove", testCaseId] or [description, "", "Remove", testCaseId]
836
- parsedTestCases.remove.push({
837
- id: tc.length >= 4 ? tc[3] : tc[1], // testCaseId could be in position 1 or 3
828
+ } else if (tc.length === 3 && tc[1] === 'Remove') {
829
+ // Remove: [description, "Remove", tcmsId]
830
+ parsed.remove.push({
831
+ id: tc[2],
838
832
  description: tc[0]
839
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
840
  }
841
- } else if (tc.length === 3 && tc[1].toLowerCase() === 'remove') {
842
- // Handle format: [description, "Remove", testCaseId]
843
- parsedTestCases.remove.push({
844
- id: tc[2] || 'N/A',
845
- description: tc[0]
846
- });
847
- }
848
- });
841
+ });
842
+
843
+ return parsed;
844
+ }
849
845
 
846
+ // Use the improved parsing function
847
+ const parsedTestCases = parseTestCases(testCases);
848
+
849
+ // Remove the old parsing logic and console.log statements that cause JSON errors
850
850
  // Initialize session state
851
851
  approvalSessions.set(sessionId, {
852
852
  status: 'pending',
853
853
  testCases: parsedTestCases,
854
- originalTestCases: JSON.parse(JSON.stringify(parsedTestCases)), // Deep copy
854
+ originalTestCases: JSON.parse(JSON.stringify(parsedTestCases)),
855
855
  server: null,
856
856
  startTime: Date.now()
857
857
  });
@@ -859,25 +859,24 @@ tool(
859
859
  const app = express();
860
860
  const port = 3001;
861
861
 
862
- // Middleware for JSON parsing
863
862
  app.use(express.json());
864
863
  app.use(express.static(path.join(__dirname, 'review-ui')));
865
864
 
866
- // Main page
867
865
  app.get("/", (req, res) => {
868
866
  res.sendFile(path.join(__dirname, 'review-ui', 'index.html'));
869
867
  });
870
868
 
871
- // API to get test cases data
872
869
  app.get("/api/testcases", (req, res) => {
873
870
  const session = approvalSessions.get(sessionId);
871
+ if (!session) {
872
+ return res.status(404).json({ error: 'Session not found' });
873
+ }
874
874
  res.json({
875
875
  sessionId: sessionId,
876
- testCases: session ? session.testCases : {}
876
+ testCases: session.testCases
877
877
  });
878
878
  });
879
879
 
880
- // Approval endpoint
881
880
  app.post(`/approve/${sessionId}`, (req, res) => {
882
881
  const session = approvalSessions.get(sessionId);
883
882
  if (session) {
@@ -885,38 +884,32 @@ tool(
885
884
  session.finalTestCases = req.body;
886
885
  approvalSessions.set(sessionId, session);
887
886
  }
888
- res.send("✓ Test cases approved successfully!");
887
+ res.json({ status: 'approved', message: 'Test cases approved successfully!' });
889
888
  });
890
889
 
891
- // Cancel endpoint
892
890
  app.post(`/cancel/${sessionId}`, (req, res) => {
893
891
  const session = approvalSessions.get(sessionId);
894
892
  if (session) {
895
893
  session.status = 'cancelled';
896
894
  approvalSessions.set(sessionId, session);
897
895
  }
898
- res.send("Review cancelled");
896
+ res.json({ status: 'cancelled', message: 'Review cancelled' });
899
897
  });
900
898
 
901
- // Start server
902
899
  const server = app.listen(port, async () => {
903
- console.log(`Test case review server started on http://localhost:${port}`);
904
-
900
+ // Remove console.log to prevent JSON parsing errors
905
901
  try {
906
902
  const { default: open } = await import('open');
907
903
  await open(`http://localhost:${port}`);
908
- console.log('Browser opened successfully');
909
904
  } catch (openError) {
910
- console.log('Failed to open browser automatically:', openError.message);
905
+ // Silent fail for browser opening
911
906
  }
912
907
  });
913
908
 
914
- // Store server reference in session
915
909
  const session = approvalSessions.get(sessionId);
916
910
  session.server = server;
917
911
  approvalSessions.set(sessionId, session);
918
912
 
919
- // Auto cleanup after 5 minutes
920
913
  setTimeout(() => {
921
914
  const session = approvalSessions.get(sessionId);
922
915
  if (session && session.status === 'pending') {
@@ -926,16 +919,23 @@ tool(
926
919
  }
927
920
  approvalSessions.set(sessionId, session);
928
921
  }
929
- }, 300000); // 5 minutes
930
-
931
- return `✅ Test case review interface opened in browser.
932
- Session ID: ${sessionId}
933
- Test Cases Count: ${testCases.length}
934
- Browser URL: http://localhost:${port}
935
- Instructions: Use check_approval_status tool to poll for approval every 25 seconds.`;
922
+ }, 300000);
923
+
924
+ // Return clean JSON response
925
+ return JSON.stringify({
926
+ status: "review_started",
927
+ sessionId: sessionId,
928
+ message: "Test case review interface opened in browser. Use check_approval_status tool to poll for approval.",
929
+ testCasesCount: testCases.length,
930
+ browserUrl: `http://localhost:${port}`,
931
+ instructions: "Poll every 25 seconds using check_approval_status tool until approved or timeout (5 minutes)"
932
+ });
936
933
 
937
934
  } catch (err) {
938
- return `❌ Error setting up review interface: ${err.message}`;
935
+ return JSON.stringify({
936
+ status: "error",
937
+ message: `Error setting up review interface: ${err.message}`
938
+ });
939
939
  }
940
940
  }
941
941
  );
@@ -948,9 +948,13 @@ tool(
948
948
  },
949
949
  async ({ sessionId }) => {
950
950
  await new Promise(resolve => setTimeout(resolve, 25000));
951
+
951
952
  const session = approvalSessions.get(sessionId);
952
953
  if (!session) {
953
- return `❌ Session not found. Invalid session ID: ${sessionId}`;
954
+ return JSON.stringify({
955
+ status: "error",
956
+ message: `Session not found. Invalid session ID: ${sessionId}`
957
+ });
954
958
  }
955
959
 
956
960
  const currentTime = Date.now();
@@ -959,36 +963,46 @@ tool(
959
963
  if (session.status === 'approved') {
960
964
  const approvedTestCases = session.finalTestCases || session.testCases;
961
965
 
962
- // Clean up session and close server
963
966
  if (session.server) {
964
967
  session.server.close();
965
968
  }
966
969
  approvalSessions.delete(sessionId);
967
970
 
968
- return `✅ Test cases approved successfully!
969
- Elapsed time: ${elapsedTime} seconds
970
- Approved test cases: ${JSON.stringify(approvedTestCases, null, 2)}`;
971
+ return JSON.stringify({
972
+ status: "approved",
973
+ message: "Test cases approved successfully!",
974
+ elapsedTime: elapsedTime,
975
+ approvedTestCases: approvedTestCases
976
+ });
971
977
  } else if (session.status === 'cancelled') {
972
- // Clean up session and close server
973
978
  if (session.server) {
974
979
  session.server.close();
975
980
  }
976
981
  approvalSessions.delete(sessionId);
977
982
 
978
- return `❌ Review was cancelled by the user. Elapsed time: ${elapsedTime} seconds`;
979
- } else if (session.status === 'timeout' || elapsedTime > 300) { // 5 minutes
980
- // Clean up session and close server
983
+ return JSON.stringify({
984
+ status: "cancelled",
985
+ message: "Review was cancelled by the user",
986
+ elapsedTime: elapsedTime
987
+ });
988
+ } else if (session.status === 'timeout' || elapsedTime > 300) {
981
989
  if (session.server) {
982
990
  session.server.close();
983
991
  }
984
992
  approvalSessions.delete(sessionId);
985
993
 
986
- return `⏰ Review session timed out. Elapsed time: ${elapsedTime} seconds`;
994
+ return JSON.stringify({
995
+ status: "timeout",
996
+ message: "Review session timed out",
997
+ elapsedTime: elapsedTime
998
+ });
987
999
  } else {
988
- return `⏳ Test cases are still pending approval.
989
- Elapsed time: ${elapsedTime} seconds
990
- Remaining time: ${Math.max(0, 300 - elapsedTime)} seconds
991
- Continue polling with check_approval_status tool.`;
1000
+ return JSON.stringify({
1001
+ status: "pending",
1002
+ message: "Test cases are still pending approval",
1003
+ elapsedTime: elapsedTime,
1004
+ remainingTime: Math.max(0, 300 - elapsedTime)
1005
+ });
992
1006
  }
993
1007
  }
994
1008
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbakka/mcp-appium",
3
- "version": "2.0.88",
3
+ "version": "2.0.90",
4
4
  "description": "Appium MCP",
5
5
  "engines": {
6
6
  "node": ">=18"