@nbakka/mcp-appium 3.0.1 → 3.0.3
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.
- package/lib/server.js +135 -85
- package/package.json +1 -1
package/lib/server.js
CHANGED
|
@@ -794,7 +794,7 @@ tool(
|
|
|
794
794
|
}
|
|
795
795
|
);
|
|
796
796
|
|
|
797
|
-
//
|
|
797
|
+
// Fix 2: Updated review_testcases tool with proper JSON handling and open module usage
|
|
798
798
|
tool(
|
|
799
799
|
"review_testcases",
|
|
800
800
|
"Open test cases in browser for manual approval.",
|
|
@@ -803,9 +803,6 @@ tool(
|
|
|
803
803
|
},
|
|
804
804
|
async ({ testCases }) => {
|
|
805
805
|
try {
|
|
806
|
-
// These should already be imported at the top of your server file
|
|
807
|
-
// const express = require('express');
|
|
808
|
-
// const open = require('open');
|
|
809
806
|
const app = express();
|
|
810
807
|
const port = 3001;
|
|
811
808
|
|
|
@@ -816,20 +813,23 @@ tool(
|
|
|
816
813
|
let approvalStatus = 'pending';
|
|
817
814
|
let finalTestCases = JSON.parse(JSON.stringify(testCases)); // Deep copy
|
|
818
815
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
try {
|
|
822
|
-
await fs.access(reviewUiPath);
|
|
823
|
-
app.use('/static', express.static(reviewUiPath));
|
|
824
|
-
} catch (err) {
|
|
825
|
-
// review-ui folder doesn't exist, continue without it
|
|
826
|
-
}
|
|
816
|
+
app.use(express.json({ limit: '10mb' })); // Increase JSON limit
|
|
817
|
+
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
827
818
|
|
|
828
|
-
|
|
829
|
-
app.use(express.static('public'));
|
|
830
|
-
|
|
831
|
-
// Main review page
|
|
819
|
+
// Main review page with fixed JSON handling
|
|
832
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
|
+
});
|
|
832
|
+
|
|
833
833
|
res.send(`
|
|
834
834
|
<!DOCTYPE html>
|
|
835
835
|
<html lang="en">
|
|
@@ -903,6 +903,8 @@ tool(
|
|
|
903
903
|
|
|
904
904
|
.content {
|
|
905
905
|
padding: 30px;
|
|
906
|
+
max-height: 70vh;
|
|
907
|
+
overflow-y: auto;
|
|
906
908
|
}
|
|
907
909
|
|
|
908
910
|
.test-case {
|
|
@@ -931,7 +933,7 @@ tool(
|
|
|
931
933
|
padding: 15px 20px;
|
|
932
934
|
border-bottom: 1px solid #e9ecef;
|
|
933
935
|
display: flex;
|
|
934
|
-
justify-content: between;
|
|
936
|
+
justify-content: space-between;
|
|
935
937
|
align-items: center;
|
|
936
938
|
}
|
|
937
939
|
|
|
@@ -968,28 +970,35 @@ tool(
|
|
|
968
970
|
}
|
|
969
971
|
|
|
970
972
|
.test-case-title {
|
|
971
|
-
font-size: 1.1rem;
|
|
972
|
-
font-weight: 500;
|
|
973
|
-
color: #212529;
|
|
974
973
|
margin-bottom: 15px;
|
|
975
|
-
line-height: 1.5;
|
|
976
974
|
}
|
|
977
975
|
|
|
978
|
-
.test-case-title
|
|
976
|
+
.test-case-title textarea {
|
|
979
977
|
width: 100%;
|
|
980
978
|
border: 1px solid #ced4da;
|
|
981
979
|
border-radius: 6px;
|
|
982
980
|
padding: 10px;
|
|
983
981
|
font-size: 1rem;
|
|
984
982
|
transition: border-color 0.3s ease;
|
|
983
|
+
min-height: 100px;
|
|
984
|
+
resize: vertical;
|
|
985
|
+
font-family: inherit;
|
|
985
986
|
}
|
|
986
987
|
|
|
987
|
-
.test-case-title
|
|
988
|
+
.test-case-title textarea:focus {
|
|
988
989
|
outline: none;
|
|
989
990
|
border-color: #4facfe;
|
|
990
991
|
box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
|
|
991
992
|
}
|
|
992
993
|
|
|
994
|
+
.original-case, .remove-case {
|
|
995
|
+
background: #f8f9fa;
|
|
996
|
+
padding: 10px;
|
|
997
|
+
border-radius: 6px;
|
|
998
|
+
margin-bottom: 15px;
|
|
999
|
+
font-size: 0.9rem;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
993
1002
|
.test-case-actions {
|
|
994
1003
|
display: flex;
|
|
995
1004
|
gap: 10px;
|
|
@@ -1087,11 +1096,11 @@ tool(
|
|
|
1087
1096
|
|
|
1088
1097
|
<div class="stats">
|
|
1089
1098
|
<div class="stat-item">
|
|
1090
|
-
<div class="stat-number" id="totalCount">${
|
|
1099
|
+
<div class="stat-number" id="totalCount">${safeTestCases.length}</div>
|
|
1091
1100
|
<div class="stat-label">Total Test Cases</div>
|
|
1092
1101
|
</div>
|
|
1093
1102
|
<div class="stat-item">
|
|
1094
|
-
<div class="stat-number" id="activeCount">${
|
|
1103
|
+
<div class="stat-number" id="activeCount">${safeTestCases.length}</div>
|
|
1095
1104
|
<div class="stat-label">Active</div>
|
|
1096
1105
|
</div>
|
|
1097
1106
|
<div class="stat-item">
|
|
@@ -1102,24 +1111,26 @@ tool(
|
|
|
1102
1111
|
|
|
1103
1112
|
<div class="content">
|
|
1104
1113
|
<div id="testCases">
|
|
1105
|
-
${
|
|
1114
|
+
${safeTestCases.map((testCase, index) => {
|
|
1115
|
+
const escapedContent = testCase[0].replace(/"/g, '"').replace(/'/g, ''').replace(/\n/g, '\\n');
|
|
1116
|
+
return `
|
|
1106
1117
|
<div class="test-case" data-index="${index}">
|
|
1107
1118
|
<div class="test-case-header">
|
|
1108
1119
|
<span class="test-case-id">Test Case #${index + 1}</span>
|
|
1109
|
-
<span class="test-case-status status-${testCase[2]
|
|
1120
|
+
<span class="test-case-status status-${testCase[2].toLowerCase()}">${testCase[2]}</span>
|
|
1110
1121
|
</div>
|
|
1111
1122
|
<div class="test-case-content">
|
|
1112
1123
|
<div class="test-case-title">
|
|
1113
|
-
<
|
|
1124
|
+
<textarea data-index="${index}" placeholder="Enter test case title and steps...">${escapedContent}</textarea>
|
|
1114
1125
|
</div>
|
|
1115
|
-
${testCase[2] === 'Modify' ? `
|
|
1126
|
+
${testCase[2] === 'Modify' && testCase[3] ? `
|
|
1116
1127
|
<div class="original-case">
|
|
1117
|
-
<small><strong>Original:</strong> ${testCase[3]
|
|
1128
|
+
<small><strong>Original:</strong> ${testCase[3]}</small>
|
|
1118
1129
|
</div>
|
|
1119
1130
|
` : ''}
|
|
1120
1131
|
${testCase[2] === 'Remove' ? `
|
|
1121
1132
|
<div class="remove-case">
|
|
1122
|
-
<small><strong>
|
|
1133
|
+
<small><strong>Marked for Removal</strong></small>
|
|
1123
1134
|
</div>
|
|
1124
1135
|
` : ''}
|
|
1125
1136
|
<div class="test-case-actions">
|
|
@@ -1127,7 +1138,8 @@ tool(
|
|
|
1127
1138
|
</div>
|
|
1128
1139
|
</div>
|
|
1129
1140
|
</div>
|
|
1130
|
-
|
|
1141
|
+
`;
|
|
1142
|
+
}).join('')}
|
|
1131
1143
|
</div>
|
|
1132
1144
|
</div>
|
|
1133
1145
|
|
|
@@ -1140,7 +1152,7 @@ tool(
|
|
|
1140
1152
|
<div class="notification" id="notification"></div>
|
|
1141
1153
|
|
|
1142
1154
|
<script>
|
|
1143
|
-
let testCases = ${JSON.stringify(
|
|
1155
|
+
let testCases = ${JSON.stringify(safeTestCases)};
|
|
1144
1156
|
let deletedIndices = new Set();
|
|
1145
1157
|
|
|
1146
1158
|
function updateStats() {
|
|
@@ -1150,7 +1162,7 @@ tool(
|
|
|
1150
1162
|
|
|
1151
1163
|
function toggleDelete(index) {
|
|
1152
1164
|
const testCaseEl = document.querySelector(\`[data-index="\${index}"]\`);
|
|
1153
|
-
const btn = testCaseEl.querySelector('.btn-delete');
|
|
1165
|
+
const btn = testCaseEl.querySelector('.btn-delete, .btn-restore');
|
|
1154
1166
|
|
|
1155
1167
|
if (deletedIndices.has(index)) {
|
|
1156
1168
|
// Restore
|
|
@@ -1176,9 +1188,9 @@ tool(
|
|
|
1176
1188
|
btn.textContent = 'Delete';
|
|
1177
1189
|
btn.className = 'btn btn-delete';
|
|
1178
1190
|
|
|
1179
|
-
// Reset
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1191
|
+
// Reset textarea value
|
|
1192
|
+
const textarea = el.querySelector('textarea');
|
|
1193
|
+
textarea.value = testCases[index][0];
|
|
1182
1194
|
});
|
|
1183
1195
|
updateStats();
|
|
1184
1196
|
}
|
|
@@ -1195,50 +1207,57 @@ tool(
|
|
|
1195
1207
|
}
|
|
1196
1208
|
|
|
1197
1209
|
function approveTestCases() {
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1210
|
+
try {
|
|
1211
|
+
// Collect updated test cases
|
|
1212
|
+
const updatedTestCases = [];
|
|
1213
|
+
document.querySelectorAll('.test-case').forEach((el, index) => {
|
|
1214
|
+
if (!deletedIndices.has(index)) {
|
|
1215
|
+
const textarea = el.querySelector('textarea');
|
|
1216
|
+
const originalTestCase = [...testCases[index]];
|
|
1217
|
+
originalTestCase[0] = textarea.value;
|
|
1218
|
+
updatedTestCases.push(originalTestCase);
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
// Send approval to server
|
|
1223
|
+
fetch('/approve', {
|
|
1224
|
+
method: 'POST',
|
|
1225
|
+
headers: {
|
|
1226
|
+
'Content-Type': 'application/json',
|
|
1227
|
+
},
|
|
1228
|
+
body: JSON.stringify({
|
|
1229
|
+
sessionId: '${sessionId}',
|
|
1230
|
+
testCases: updatedTestCases
|
|
1231
|
+
})
|
|
1218
1232
|
})
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1233
|
+
.then(response => response.json())
|
|
1234
|
+
.then(data => {
|
|
1235
|
+
if (data.success) {
|
|
1236
|
+
showNotification('Test cases approved successfully!');
|
|
1237
|
+
setTimeout(() => {
|
|
1238
|
+
window.close();
|
|
1239
|
+
}, 2000);
|
|
1240
|
+
} else {
|
|
1241
|
+
showNotification('Error approving test cases', 'error');
|
|
1242
|
+
}
|
|
1243
|
+
})
|
|
1244
|
+
.catch(error => {
|
|
1228
1245
|
showNotification('Error approving test cases', 'error');
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
showNotification('Error
|
|
1246
|
+
console.error('Error:', error);
|
|
1247
|
+
});
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
showNotification('Error processing test cases', 'error');
|
|
1233
1250
|
console.error('Error:', error);
|
|
1234
|
-
}
|
|
1251
|
+
}
|
|
1235
1252
|
}
|
|
1236
1253
|
|
|
1237
|
-
// Update test cases when
|
|
1254
|
+
// Update test cases when textarea changes
|
|
1238
1255
|
document.addEventListener('input', function(e) {
|
|
1239
|
-
if (e.target.tagName === '
|
|
1256
|
+
if (e.target.tagName === 'TEXTAREA') {
|
|
1240
1257
|
const index = parseInt(e.target.getAttribute('data-index'));
|
|
1241
|
-
testCases[index]
|
|
1258
|
+
if (!isNaN(index) && testCases[index]) {
|
|
1259
|
+
testCases[index][0] = e.target.value;
|
|
1260
|
+
}
|
|
1242
1261
|
}
|
|
1243
1262
|
});
|
|
1244
1263
|
</script>
|
|
@@ -1247,10 +1266,15 @@ tool(
|
|
|
1247
1266
|
`);
|
|
1248
1267
|
});
|
|
1249
1268
|
|
|
1250
|
-
// Approval endpoint
|
|
1269
|
+
// Approval endpoint with better error handling
|
|
1251
1270
|
app.post('/approve', (req, res) => {
|
|
1252
1271
|
try {
|
|
1253
|
-
const { testCases: approvedTestCases } = req.body;
|
|
1272
|
+
const { testCases: approvedTestCases, sessionId: receivedSessionId } = req.body;
|
|
1273
|
+
|
|
1274
|
+
if (receivedSessionId !== sessionId) {
|
|
1275
|
+
return res.status(400).json({ success: false, message: 'Invalid session ID' });
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1254
1278
|
finalTestCases = approvedTestCases;
|
|
1255
1279
|
approvalStatus = 'approved';
|
|
1256
1280
|
|
|
@@ -1269,6 +1293,7 @@ tool(
|
|
|
1269
1293
|
server.close();
|
|
1270
1294
|
}, 3000);
|
|
1271
1295
|
} catch (error) {
|
|
1296
|
+
console.error('Approval error:', error);
|
|
1272
1297
|
res.status(500).json({ success: false, message: error.message });
|
|
1273
1298
|
}
|
|
1274
1299
|
});
|
|
@@ -1278,8 +1303,26 @@ tool(
|
|
|
1278
1303
|
console.log(`Test case review server running at http://localhost:${port}`);
|
|
1279
1304
|
});
|
|
1280
1305
|
|
|
1281
|
-
//
|
|
1282
|
-
|
|
1306
|
+
// Handle server errors
|
|
1307
|
+
server.on('error', (error) => {
|
|
1308
|
+
if (error.code === 'EADDRINUSE') {
|
|
1309
|
+
// Try a different port
|
|
1310
|
+
const newPort = port + Math.floor(Math.random() * 100);
|
|
1311
|
+
return app.listen(newPort, () => {
|
|
1312
|
+
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));
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
throw error;
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
// Open browser with proper error handling
|
|
1320
|
+
try {
|
|
1321
|
+
await open(`http://localhost:${port}`);
|
|
1322
|
+
} catch (openError) {
|
|
1323
|
+
console.error('Failed to open browser automatically:', openError.message);
|
|
1324
|
+
// Continue without opening browser - user can manually navigate to the URL
|
|
1325
|
+
}
|
|
1283
1326
|
|
|
1284
1327
|
// Store session globally for status checking
|
|
1285
1328
|
global.approvalSessions = global.approvalSessions || {};
|
|
@@ -1289,15 +1332,18 @@ tool(
|
|
|
1289
1332
|
timestamp: Date.now()
|
|
1290
1333
|
};
|
|
1291
1334
|
|
|
1292
|
-
return `✅ Test case review session started. Session ID: ${sessionId}.
|
|
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.'}`;
|
|
1293
1338
|
|
|
1294
1339
|
} catch (err) {
|
|
1340
|
+
console.error('Review tool error:', err);
|
|
1295
1341
|
return `❌ Error starting test case review: ${err.message}`;
|
|
1296
1342
|
}
|
|
1297
1343
|
}
|
|
1298
1344
|
);
|
|
1299
1345
|
|
|
1300
|
-
//
|
|
1346
|
+
// Fix 3: Updated check_approval_status tool with better error handling
|
|
1301
1347
|
tool(
|
|
1302
1348
|
"check_approval_status",
|
|
1303
1349
|
"Check the approval status of test cases review session (waits 25 seconds before checking)",
|
|
@@ -1324,19 +1370,23 @@ tool(
|
|
|
1324
1370
|
sessionId: sessionId
|
|
1325
1371
|
};
|
|
1326
1372
|
|
|
1373
|
+
// Format the approved test cases properly
|
|
1374
|
+
const formattedTestCases = result.testCases.map((tc, index) => {
|
|
1375
|
+
const title = Array.isArray(tc) ? tc[0] : String(tc);
|
|
1376
|
+
const status = Array.isArray(tc) && tc[2] ? ` (${tc[2]})` : '';
|
|
1377
|
+
return `${index + 1}. ${title}${status}`;
|
|
1378
|
+
}).join('\n');
|
|
1379
|
+
|
|
1327
1380
|
// Clean up session after returning result
|
|
1328
1381
|
delete global.approvalSessions[sessionId];
|
|
1329
1382
|
|
|
1330
|
-
return `✅ Test cases approved successfully!\n\nApproved ${result.approvedCount} test cases:\n\n${
|
|
1331
|
-
result.testCases.map((tc, index) =>
|
|
1332
|
-
`${index + 1}. ${tc[0]}${tc[2] ? ` (${tc[2]})` : ''}`
|
|
1333
|
-
).join('\n')
|
|
1334
|
-
}\n\nSession ID: ${sessionId}`;
|
|
1383
|
+
return `✅ Test cases approved successfully!\n\nApproved ${result.approvedCount} test cases:\n\n${formattedTestCases}\n\nSession completed: ${sessionId}`;
|
|
1335
1384
|
} else {
|
|
1336
1385
|
return "⏳ Still waiting for approval. The review session is active but not yet approved. Please complete the review in the browser.";
|
|
1337
1386
|
}
|
|
1338
1387
|
|
|
1339
1388
|
} catch (err) {
|
|
1389
|
+
console.error('Check approval status error:', err);
|
|
1340
1390
|
return `❌ Error checking approval status: ${err.message}`;
|
|
1341
1391
|
}
|
|
1342
1392
|
}
|