@nbakka/mcp-appium 3.0.4 → 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 +119 -39
  2. package/package.json +1 -1
package/lib/server.js CHANGED
@@ -818,21 +818,33 @@ tool(
818
818
  app.use(express.json({ limit: '10mb' })); // Increase JSON limit
819
819
  app.use(express.urlencoded({ extended: true, limit: '10mb' }));
820
820
 
821
- // Main review page with fixed JSON handling
822
- app.get('/', (req, res) => {
823
- // Safely stringify testCases to avoid JSON issues
824
- const safeTestCases = testCases.map((testCase, index) => {
825
- // Ensure each test case is an array with at least 3 elements
826
- const safeCase = Array.isArray(testCase) ? testCase : [testCase];
827
- return [
828
- String(safeCase[0] || `Test Case ${index + 1}`), // Title
829
- String(safeCase[1] || ''), // Description/Steps
830
- String(safeCase[2] || 'New'), // Status
831
- String(safeCase[3] || '') // Original case (for modifications)
832
- ];
833
- });
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
+ });
834
843
 
835
- res.send(`
844
+ // Main review page with proper handling
845
+ app.get('/', (req, res) => {
846
+ try {
847
+ res.send(`
836
848
  <!DOCTYPE html>
837
849
  <html lang="en">
838
850
  <head>
@@ -982,7 +994,7 @@ tool(
982
994
  padding: 10px;
983
995
  font-size: 1rem;
984
996
  transition: border-color 0.3s ease;
985
- min-height: 100px;
997
+ min-height: 120px;
986
998
  resize: vertical;
987
999
  font-family: inherit;
988
1000
  }
@@ -993,8 +1005,18 @@ tool(
993
1005
  box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
994
1006
  }
995
1007
 
996
- .original-case, .remove-case {
997
- 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;
998
1020
  padding: 10px;
999
1021
  border-radius: 6px;
1000
1022
  margin-bottom: 15px;
@@ -1098,11 +1120,11 @@ tool(
1098
1120
 
1099
1121
  <div class="stats">
1100
1122
  <div class="stat-item">
1101
- <div class="stat-number" id="totalCount">${safeTestCases.length}</div>
1123
+ <div class="stat-number" id="totalCount">${processedTestCases.length}</div>
1102
1124
  <div class="stat-label">Total Test Cases</div>
1103
1125
  </div>
1104
1126
  <div class="stat-item">
1105
- <div class="stat-number" id="activeCount">${safeTestCases.length}</div>
1127
+ <div class="stat-number" id="activeCount">${processedTestCases.length}</div>
1106
1128
  <div class="stat-label">Active</div>
1107
1129
  </div>
1108
1130
  <div class="stat-item">
@@ -1113,26 +1135,49 @@ tool(
1113
1135
 
1114
1136
  <div class="content">
1115
1137
  <div id="testCases">
1116
- ${safeTestCases.map((testCase, index) => {
1117
- 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
+
1118
1161
  return `
1119
1162
  <div class="test-case" data-index="${index}">
1120
1163
  <div class="test-case-header">
1121
1164
  <span class="test-case-id">Test Case #${index + 1}</span>
1122
- <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>
1123
1166
  </div>
1124
1167
  <div class="test-case-content">
1125
1168
  <div class="test-case-title">
1126
- <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>
1127
1170
  </div>
1128
- ${testCase[2] === 'Modify' && testCase[3] ? `
1171
+ ${testCase.status === 'Modify' && testCase.originalCase ? `
1129
1172
  <div class="original-case">
1130
- <small><strong>Original:</strong> ${testCase[3]}</small>
1173
+ <strong>Original Test Case:</strong><br>
1174
+ ${safeOriginal}
1131
1175
  </div>
1132
1176
  ` : ''}
1133
- ${testCase[2] === 'Remove' ? `
1177
+ ${testCase.status === 'Remove' ? `
1134
1178
  <div class="remove-case">
1135
- <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.
1136
1181
  </div>
1137
1182
  ` : ''}
1138
1183
  <div class="test-case-actions">
@@ -1154,7 +1199,7 @@ tool(
1154
1199
  <div class="notification" id="notification"></div>
1155
1200
 
1156
1201
  <script>
1157
- let testCases = ${JSON.stringify(safeTestCases)};
1202
+ let testCases = ${JSON.stringify(processedTestCases)};
1158
1203
  let deletedIndices = new Set();
1159
1204
 
1160
1205
  function updateStats() {
@@ -1192,7 +1237,9 @@ tool(
1192
1237
 
1193
1238
  // Reset textarea value
1194
1239
  const textarea = el.querySelector('textarea');
1195
- 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;
1196
1243
  });
1197
1244
  updateStats();
1198
1245
  }
@@ -1215,9 +1262,17 @@ tool(
1215
1262
  document.querySelectorAll('.test-case').forEach((el, index) => {
1216
1263
  if (!deletedIndices.has(index)) {
1217
1264
  const textarea = el.querySelector('textarea');
1218
- const originalTestCase = [...testCases[index]];
1219
- originalTestCase[0] = textarea.value;
1220
- 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);
1221
1276
  }
1222
1277
  });
1223
1278
 
@@ -1258,14 +1313,22 @@ tool(
1258
1313
  if (e.target.tagName === 'TEXTAREA') {
1259
1314
  const index = parseInt(e.target.getAttribute('data-index'));
1260
1315
  if (!isNaN(index) && testCases[index]) {
1261
- 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
+ }
1262
1321
  }
1263
1322
  }
1264
1323
  });
1265
1324
  </script>
1266
1325
  </body>
1267
1326
  </html>
1268
- `);
1327
+ `);
1328
+ } catch (error) {
1329
+ console.error('Error rendering page:', error);
1330
+ res.status(500).send('Error rendering page');
1331
+ }
1269
1332
  });
1270
1333
 
1271
1334
  // Approval endpoint with better error handling
@@ -1300,9 +1363,25 @@ tool(
1300
1363
  }
1301
1364
  });
1302
1365
 
1303
- // Start server
1304
- const server = app.listen(port, () => {
1305
- 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}`);
1306
1385
  });
1307
1386
 
1308
1387
  // Handle server errors
@@ -1310,7 +1389,7 @@ tool(
1310
1389
  if (error.code === 'EADDRINUSE') {
1311
1390
  // Try a different port
1312
1391
  const newPort = port + Math.floor(Math.random() * 100);
1313
- return app.listen(newPort, () => {
1392
+ const newServer = app.listen(newPort, () => {
1314
1393
  console.log(`Test case review server running at http://localhost:${newPort}`);
1315
1394
  try {
1316
1395
  openBrowser(`http://localhost:${newPort}`).catch(err => console.error('Failed to open browser:', err));
@@ -1318,6 +1397,7 @@ tool(
1318
1397
  console.error('Failed to open browser automatically:', e.message);
1319
1398
  }
1320
1399
  });
1400
+ return newServer;
1321
1401
  }
1322
1402
  throw error;
1323
1403
  });
@@ -1336,7 +1416,7 @@ tool(
1336
1416
  global.approvalSessions = global.approvalSessions || {};
1337
1417
  global.approvalSessions[sessionId] = {
1338
1418
  status: 'pending',
1339
- testCases: testCases,
1419
+ testCases: processedTestCases,
1340
1420
  timestamp: Date.now()
1341
1421
  };
1342
1422
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbakka/mcp-appium",
3
- "version": "3.0.4",
3
+ "version": "3.0.5",
4
4
  "description": "Appium MCP",
5
5
  "engines": {
6
6
  "node": ">=18"