@nbakka/mcp-appium 3.0.3 → 3.0.5

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.
Files changed (2) hide show
  1. package/lib/server.js +134 -48
  2. package/package.json +1 -1
package/lib/server.js CHANGED
@@ -18,7 +18,9 @@ const { google } = require('googleapis');
18
18
  const axios = require('axios');
19
19
  const OpenAI = require("openai");
20
20
  const express = require('express');
21
- const open = require('open');
21
+ // Fixed: support ESM default export of 'open'
22
+ const openImport = require('open');
23
+ const openBrowser = openImport.default || openImport;
22
24
  const getAgentVersion = () => {
23
25
  const json = require("../package.json");
24
26
  return json.version;
@@ -816,21 +818,33 @@ tool(
816
818
  app.use(express.json({ limit: '10mb' })); // Increase JSON limit
817
819
  app.use(express.urlencoded({ extended: true, limit: '10mb' }));
818
820
 
819
- // Main review page with fixed JSON handling
820
- app.get('/', (req, res) => {
821
- // Safely stringify testCases to avoid JSON issues
822
- const safeTestCases = testCases.map((testCase, index) => {
823
- // Ensure each test case is an array with at least 3 elements
824
- const safeCase = Array.isArray(testCase) ? testCase : [testCase];
825
- return [
826
- String(safeCase[0] || `Test Case ${index + 1}`), // Title
827
- String(safeCase[1] || ''), // Description/Steps
828
- String(safeCase[2] || 'New'), // Status
829
- String(safeCase[3] || '') // Original case (for modifications)
830
- ];
831
- });
821
+ // Process test cases - handle the specific format properly
822
+ const processedTestCases = testCases.map((testCase, index) => {
823
+ // Each testCase is an array like ["title", "description", "status", "originalCase"]
824
+ if (Array.isArray(testCase)) {
825
+ return {
826
+ title: testCase[0] || `Test Case ${index + 1}`,
827
+ description: testCase[1] || '',
828
+ status: testCase[2] || 'New',
829
+ originalCase: testCase[3] || '',
830
+ index: index
831
+ };
832
+ } else {
833
+ // Fallback for unexpected format
834
+ return {
835
+ title: String(testCase) || `Test Case ${index + 1}`,
836
+ description: '',
837
+ status: 'New',
838
+ originalCase: '',
839
+ index: index
840
+ };
841
+ }
842
+ });
832
843
 
833
- res.send(`
844
+ // Main review page with proper handling
845
+ app.get('/', (req, res) => {
846
+ try {
847
+ res.send(`
834
848
  <!DOCTYPE html>
835
849
  <html lang="en">
836
850
  <head>
@@ -980,7 +994,7 @@ tool(
980
994
  padding: 10px;
981
995
  font-size: 1rem;
982
996
  transition: border-color 0.3s ease;
983
- min-height: 100px;
997
+ min-height: 120px;
984
998
  resize: vertical;
985
999
  font-family: inherit;
986
1000
  }
@@ -991,8 +1005,18 @@ tool(
991
1005
  box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
992
1006
  }
993
1007
 
994
- .original-case, .remove-case {
995
- background: #f8f9fa;
1008
+ .original-case {
1009
+ background: #fff3cd;
1010
+ border: 1px solid #ffeaa7;
1011
+ padding: 10px;
1012
+ border-radius: 6px;
1013
+ margin-bottom: 15px;
1014
+ font-size: 0.9rem;
1015
+ }
1016
+
1017
+ .remove-case {
1018
+ background: #f8d7da;
1019
+ border: 1px solid #f5c6cb;
996
1020
  padding: 10px;
997
1021
  border-radius: 6px;
998
1022
  margin-bottom: 15px;
@@ -1096,11 +1120,11 @@ tool(
1096
1120
 
1097
1121
  <div class="stats">
1098
1122
  <div class="stat-item">
1099
- <div class="stat-number" id="totalCount">${safeTestCases.length}</div>
1123
+ <div class="stat-number" id="totalCount">${processedTestCases.length}</div>
1100
1124
  <div class="stat-label">Total Test Cases</div>
1101
1125
  </div>
1102
1126
  <div class="stat-item">
1103
- <div class="stat-number" id="activeCount">${safeTestCases.length}</div>
1127
+ <div class="stat-number" id="activeCount">${processedTestCases.length}</div>
1104
1128
  <div class="stat-label">Active</div>
1105
1129
  </div>
1106
1130
  <div class="stat-item">
@@ -1111,26 +1135,49 @@ tool(
1111
1135
 
1112
1136
  <div class="content">
1113
1137
  <div id="testCases">
1114
- ${safeTestCases.map((testCase, index) => {
1115
- const escapedContent = testCase[0].replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/\n/g, '\\n');
1138
+ ${processedTestCases.map((testCase, index) => {
1139
+ // Safely escape HTML characters
1140
+ const safeTitle = testCase.title
1141
+ .replace(/&/g, '&amp;')
1142
+ .replace(/</g, '&lt;')
1143
+ .replace(/>/g, '&gt;')
1144
+ .replace(/"/g, '&quot;')
1145
+ .replace(/'/g, '&#39;');
1146
+
1147
+ const safeDescription = testCase.description
1148
+ .replace(/&/g, '&amp;')
1149
+ .replace(/</g, '&lt;')
1150
+ .replace(/>/g, '&gt;')
1151
+ .replace(/"/g, '&quot;')
1152
+ .replace(/'/g, '&#39;');
1153
+
1154
+ const safeOriginal = testCase.originalCase
1155
+ .replace(/&/g, '&amp;')
1156
+ .replace(/</g, '&lt;')
1157
+ .replace(/>/g, '&gt;')
1158
+ .replace(/"/g, '&quot;')
1159
+ .replace(/'/g, '&#39;');
1160
+
1116
1161
  return `
1117
1162
  <div class="test-case" data-index="${index}">
1118
1163
  <div class="test-case-header">
1119
1164
  <span class="test-case-id">Test Case #${index + 1}</span>
1120
- <span class="test-case-status status-${testCase[2].toLowerCase()}">${testCase[2]}</span>
1165
+ <span class="test-case-status status-${testCase.status.toLowerCase()}">${testCase.status}</span>
1121
1166
  </div>
1122
1167
  <div class="test-case-content">
1123
1168
  <div class="test-case-title">
1124
- <textarea data-index="${index}" placeholder="Enter test case title and steps...">${escapedContent}</textarea>
1169
+ <textarea data-index="${index}" placeholder="Enter test case title and steps...">${safeTitle}${safeDescription ? '\n\nSteps:\n' + safeDescription : ''}</textarea>
1125
1170
  </div>
1126
- ${testCase[2] === 'Modify' && testCase[3] ? `
1171
+ ${testCase.status === 'Modify' && testCase.originalCase ? `
1127
1172
  <div class="original-case">
1128
- <small><strong>Original:</strong> ${testCase[3]}</small>
1173
+ <strong>Original Test Case:</strong><br>
1174
+ ${safeOriginal}
1129
1175
  </div>
1130
1176
  ` : ''}
1131
- ${testCase[2] === 'Remove' ? `
1177
+ ${testCase.status === 'Remove' ? `
1132
1178
  <div class="remove-case">
1133
- <small><strong>Marked for Removal</strong></small>
1179
+ <strong>⚠️ Marked for Removal</strong><br>
1180
+ This test case is scheduled to be removed. You can restore it using the button below.
1134
1181
  </div>
1135
1182
  ` : ''}
1136
1183
  <div class="test-case-actions">
@@ -1152,7 +1199,7 @@ tool(
1152
1199
  <div class="notification" id="notification"></div>
1153
1200
 
1154
1201
  <script>
1155
- let testCases = ${JSON.stringify(safeTestCases)};
1202
+ let testCases = ${JSON.stringify(processedTestCases)};
1156
1203
  let deletedIndices = new Set();
1157
1204
 
1158
1205
  function updateStats() {
@@ -1190,7 +1237,9 @@ tool(
1190
1237
 
1191
1238
  // Reset textarea value
1192
1239
  const textarea = el.querySelector('textarea');
1193
- textarea.value = testCases[index][0];
1240
+ const originalTestCase = testCases[index];
1241
+ const resetValue = originalTestCase.title + (originalTestCase.description ? '\n\nSteps:\n' + originalTestCase.description : '');
1242
+ textarea.value = resetValue;
1194
1243
  });
1195
1244
  updateStats();
1196
1245
  }
@@ -1213,9 +1262,17 @@ tool(
1213
1262
  document.querySelectorAll('.test-case').forEach((el, index) => {
1214
1263
  if (!deletedIndices.has(index)) {
1215
1264
  const textarea = el.querySelector('textarea');
1216
- const originalTestCase = [...testCases[index]];
1217
- originalTestCase[0] = textarea.value;
1218
- updatedTestCases.push(originalTestCase);
1265
+ const originalTestCase = testCases[index];
1266
+
1267
+ // Create the updated test case array in the original format
1268
+ const updatedCase = [
1269
+ textarea.value.trim(), // Updated title/content
1270
+ originalTestCase.description, // Keep original description
1271
+ originalTestCase.status, // Keep original status
1272
+ originalTestCase.originalCase // Keep original case reference
1273
+ ];
1274
+
1275
+ updatedTestCases.push(updatedCase);
1219
1276
  }
1220
1277
  });
1221
1278
 
@@ -1235,7 +1292,7 @@ tool(
1235
1292
  if (data.success) {
1236
1293
  showNotification('Test cases approved successfully!');
1237
1294
  setTimeout(() => {
1238
- window.close();
1295
+ window.close();
1239
1296
  }, 2000);
1240
1297
  } else {
1241
1298
  showNotification('Error approving test cases', 'error');
@@ -1256,14 +1313,22 @@ tool(
1256
1313
  if (e.target.tagName === 'TEXTAREA') {
1257
1314
  const index = parseInt(e.target.getAttribute('data-index'));
1258
1315
  if (!isNaN(index) && testCases[index]) {
1259
- testCases[index][0] = e.target.value;
1316
+ testCases[index].title = e.target.value.split('\n\nSteps:\n')[0];
1317
+ const stepsIndex = e.target.value.indexOf('\n\nSteps:\n');
1318
+ if (stepsIndex !== -1) {
1319
+ testCases[index].description = e.target.value.substring(stepsIndex + 9);
1320
+ }
1260
1321
  }
1261
1322
  }
1262
1323
  });
1263
1324
  </script>
1264
1325
  </body>
1265
1326
  </html>
1266
- `);
1327
+ `);
1328
+ } catch (error) {
1329
+ console.error('Error rendering page:', error);
1330
+ res.status(500).send('Error rendering page');
1331
+ }
1267
1332
  });
1268
1333
 
1269
1334
  // Approval endpoint with better error handling
@@ -1298,9 +1363,25 @@ tool(
1298
1363
  }
1299
1364
  });
1300
1365
 
1301
- // Start server
1302
- const server = app.listen(port, () => {
1303
- console.log(`Test case review server running at http://localhost:${port}`);
1366
+ // Error handling middleware
1367
+ app.use((err, req, res, next) => {
1368
+ console.error('Express error:', err);
1369
+ res.status(500).json({ error: 'Internal server error' });
1370
+ });
1371
+
1372
+ // 404 handler
1373
+ app.use((req, res) => {
1374
+ res.status(404).json({ error: 'Not found' });
1375
+ });
1376
+
1377
+ // Start server with better error handling
1378
+ const server = app.listen(port, (err) => {
1379
+ if (err) {
1380
+ console.error('Failed to start server:', err);
1381
+ return;
1382
+ }
1383
+ console.log(`✅ Test case review session started. Session ID: ${sessionId}.`);
1384
+ console.log(`Server running at http://localhost:${port}`);
1304
1385
  });
1305
1386
 
1306
1387
  // Handle server errors
@@ -1308,19 +1389,26 @@ tool(
1308
1389
  if (error.code === 'EADDRINUSE') {
1309
1390
  // Try a different port
1310
1391
  const newPort = port + Math.floor(Math.random() * 100);
1311
- return app.listen(newPort, () => {
1392
+ const newServer = app.listen(newPort, () => {
1312
1393
  console.log(`Test case review server running at http://localhost:${newPort}`);
1313
- open(`http://localhost:${newPort}`).catch(err => console.error('Failed to open browser:', err));
1394
+ try {
1395
+ openBrowser(`http://localhost:${newPort}`).catch(err => console.error('Failed to open browser:', err));
1396
+ } catch (e) {
1397
+ console.error('Failed to open browser automatically:', e.message);
1398
+ }
1314
1399
  });
1400
+ return newServer;
1315
1401
  }
1316
1402
  throw error;
1317
1403
  });
1318
1404
 
1319
1405
  // Open browser with proper error handling
1406
+ let openAttemptFailed = false;
1320
1407
  try {
1321
- await open(`http://localhost:${port}`);
1322
- } catch (openError) {
1323
- console.error('Failed to open browser automatically:', openError.message);
1408
+ await openBrowser(`http://localhost:${port}`);
1409
+ } catch (err) {
1410
+ openAttemptFailed = true;
1411
+ console.error('Failed to open browser automatically:', err.message);
1324
1412
  // Continue without opening browser - user can manually navigate to the URL
1325
1413
  }
1326
1414
 
@@ -1328,13 +1416,11 @@ tool(
1328
1416
  global.approvalSessions = global.approvalSessions || {};
1329
1417
  global.approvalSessions[sessionId] = {
1330
1418
  status: 'pending',
1331
- testCases: testCases,
1419
+ testCases: processedTestCases,
1332
1420
  timestamp: Date.now()
1333
1421
  };
1334
1422
 
1335
- return `✅ Test case review session started. Session ID: ${sessionId}.
1336
- Server running at http://localhost:${port}
1337
- ${openError ? 'Please manually open the URL in your browser.' : 'Browser should open automatically.'}`;
1423
+ return `✅ Test case review session started. Session ID: ${sessionId}.\nServer running at http://localhost:${port}\n${openAttemptFailed ? 'Please manually open the URL in your browser.' : 'Browser should open automatically.'}`;
1338
1424
 
1339
1425
  } catch (err) {
1340
1426
  console.error('Review tool error:', err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbakka/mcp-appium",
3
- "version": "3.0.3",
3
+ "version": "3.0.5",
4
4
  "description": "Appium MCP",
5
5
  "engines": {
6
6
  "node": ">=18"