@nbakka/mcp-appium 3.0.0 → 3.0.2
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 +142 -82
- 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,8 +803,6 @@ tool(
|
|
|
803
803
|
},
|
|
804
804
|
async ({ testCases }) => {
|
|
805
805
|
try {
|
|
806
|
-
const express = require('express');
|
|
807
|
-
const open = require('open');
|
|
808
806
|
const app = express();
|
|
809
807
|
const port = 3001;
|
|
810
808
|
|
|
@@ -815,20 +813,23 @@ tool(
|
|
|
815
813
|
let approvalStatus = 'pending';
|
|
816
814
|
let finalTestCases = JSON.parse(JSON.stringify(testCases)); // Deep copy
|
|
817
815
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
try {
|
|
821
|
-
await fs.access(reviewUiPath);
|
|
822
|
-
app.use('/static', express.static(reviewUiPath));
|
|
823
|
-
} catch (err) {
|
|
824
|
-
// review-ui folder doesn't exist, continue without it
|
|
825
|
-
}
|
|
816
|
+
app.use(express.json({ limit: '10mb' })); // Increase JSON limit
|
|
817
|
+
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
826
818
|
|
|
827
|
-
|
|
828
|
-
app.use(express.static('public'));
|
|
829
|
-
|
|
830
|
-
// Main review page
|
|
819
|
+
// Main review page with fixed JSON handling
|
|
831
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
|
+
|
|
832
833
|
res.send(`
|
|
833
834
|
<!DOCTYPE html>
|
|
834
835
|
<html lang="en">
|
|
@@ -902,6 +903,8 @@ tool(
|
|
|
902
903
|
|
|
903
904
|
.content {
|
|
904
905
|
padding: 30px;
|
|
906
|
+
max-height: 70vh;
|
|
907
|
+
overflow-y: auto;
|
|
905
908
|
}
|
|
906
909
|
|
|
907
910
|
.test-case {
|
|
@@ -930,7 +933,7 @@ tool(
|
|
|
930
933
|
padding: 15px 20px;
|
|
931
934
|
border-bottom: 1px solid #e9ecef;
|
|
932
935
|
display: flex;
|
|
933
|
-
justify-content: between;
|
|
936
|
+
justify-content: space-between;
|
|
934
937
|
align-items: center;
|
|
935
938
|
}
|
|
936
939
|
|
|
@@ -967,28 +970,35 @@ tool(
|
|
|
967
970
|
}
|
|
968
971
|
|
|
969
972
|
.test-case-title {
|
|
970
|
-
font-size: 1.1rem;
|
|
971
|
-
font-weight: 500;
|
|
972
|
-
color: #212529;
|
|
973
973
|
margin-bottom: 15px;
|
|
974
|
-
line-height: 1.5;
|
|
975
974
|
}
|
|
976
975
|
|
|
977
|
-
.test-case-title
|
|
976
|
+
.test-case-title textarea {
|
|
978
977
|
width: 100%;
|
|
979
978
|
border: 1px solid #ced4da;
|
|
980
979
|
border-radius: 6px;
|
|
981
980
|
padding: 10px;
|
|
982
981
|
font-size: 1rem;
|
|
983
982
|
transition: border-color 0.3s ease;
|
|
983
|
+
min-height: 100px;
|
|
984
|
+
resize: vertical;
|
|
985
|
+
font-family: inherit;
|
|
984
986
|
}
|
|
985
987
|
|
|
986
|
-
.test-case-title
|
|
988
|
+
.test-case-title textarea:focus {
|
|
987
989
|
outline: none;
|
|
988
990
|
border-color: #4facfe;
|
|
989
991
|
box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
|
|
990
992
|
}
|
|
991
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
|
+
|
|
992
1002
|
.test-case-actions {
|
|
993
1003
|
display: flex;
|
|
994
1004
|
gap: 10px;
|
|
@@ -1086,11 +1096,11 @@ tool(
|
|
|
1086
1096
|
|
|
1087
1097
|
<div class="stats">
|
|
1088
1098
|
<div class="stat-item">
|
|
1089
|
-
<div class="stat-number" id="totalCount">${
|
|
1099
|
+
<div class="stat-number" id="totalCount">${safeTestCases.length}</div>
|
|
1090
1100
|
<div class="stat-label">Total Test Cases</div>
|
|
1091
1101
|
</div>
|
|
1092
1102
|
<div class="stat-item">
|
|
1093
|
-
<div class="stat-number" id="activeCount">${
|
|
1103
|
+
<div class="stat-number" id="activeCount">${safeTestCases.length}</div>
|
|
1094
1104
|
<div class="stat-label">Active</div>
|
|
1095
1105
|
</div>
|
|
1096
1106
|
<div class="stat-item">
|
|
@@ -1101,22 +1111,35 @@ tool(
|
|
|
1101
1111
|
|
|
1102
1112
|
<div class="content">
|
|
1103
1113
|
<div id="testCases">
|
|
1104
|
-
${
|
|
1114
|
+
${safeTestCases.map((testCase, index) => {
|
|
1115
|
+
const escapedContent = testCase[0].replace(/"/g, '"').replace(/'/g, ''').replace(/\n/g, '\\n');
|
|
1116
|
+
return `
|
|
1105
1117
|
<div class="test-case" data-index="${index}">
|
|
1106
1118
|
<div class="test-case-header">
|
|
1107
1119
|
<span class="test-case-id">Test Case #${index + 1}</span>
|
|
1108
|
-
<span class="test-case-status status-${testCase[2]
|
|
1120
|
+
<span class="test-case-status status-${testCase[2].toLowerCase()}">${testCase[2]}</span>
|
|
1109
1121
|
</div>
|
|
1110
1122
|
<div class="test-case-content">
|
|
1111
1123
|
<div class="test-case-title">
|
|
1112
|
-
<
|
|
1124
|
+
<textarea data-index="${index}" placeholder="Enter test case title and steps...">${escapedContent}</textarea>
|
|
1113
1125
|
</div>
|
|
1126
|
+
${testCase[2] === 'Modify' && testCase[3] ? `
|
|
1127
|
+
<div class="original-case">
|
|
1128
|
+
<small><strong>Original:</strong> ${testCase[3]}</small>
|
|
1129
|
+
</div>
|
|
1130
|
+
` : ''}
|
|
1131
|
+
${testCase[2] === 'Remove' ? `
|
|
1132
|
+
<div class="remove-case">
|
|
1133
|
+
<small><strong>Marked for Removal</strong></small>
|
|
1134
|
+
</div>
|
|
1135
|
+
` : ''}
|
|
1114
1136
|
<div class="test-case-actions">
|
|
1115
1137
|
<button class="btn btn-delete" onclick="toggleDelete(${index})">Delete</button>
|
|
1116
1138
|
</div>
|
|
1117
1139
|
</div>
|
|
1118
1140
|
</div>
|
|
1119
|
-
|
|
1141
|
+
`;
|
|
1142
|
+
}).join('')}
|
|
1120
1143
|
</div>
|
|
1121
1144
|
</div>
|
|
1122
1145
|
|
|
@@ -1129,7 +1152,7 @@ tool(
|
|
|
1129
1152
|
<div class="notification" id="notification"></div>
|
|
1130
1153
|
|
|
1131
1154
|
<script>
|
|
1132
|
-
let testCases = ${JSON.stringify(
|
|
1155
|
+
let testCases = ${JSON.stringify(safeTestCases)};
|
|
1133
1156
|
let deletedIndices = new Set();
|
|
1134
1157
|
|
|
1135
1158
|
function updateStats() {
|
|
@@ -1139,7 +1162,7 @@ tool(
|
|
|
1139
1162
|
|
|
1140
1163
|
function toggleDelete(index) {
|
|
1141
1164
|
const testCaseEl = document.querySelector(\`[data-index="\${index}"]\`);
|
|
1142
|
-
const btn = testCaseEl.querySelector('.btn-delete');
|
|
1165
|
+
const btn = testCaseEl.querySelector('.btn-delete, .btn-restore');
|
|
1143
1166
|
|
|
1144
1167
|
if (deletedIndices.has(index)) {
|
|
1145
1168
|
// Restore
|
|
@@ -1165,9 +1188,9 @@ tool(
|
|
|
1165
1188
|
btn.textContent = 'Delete';
|
|
1166
1189
|
btn.className = 'btn btn-delete';
|
|
1167
1190
|
|
|
1168
|
-
// Reset
|
|
1169
|
-
const
|
|
1170
|
-
|
|
1191
|
+
// Reset textarea value
|
|
1192
|
+
const textarea = el.querySelector('textarea');
|
|
1193
|
+
textarea.value = testCases[index][0];
|
|
1171
1194
|
});
|
|
1172
1195
|
updateStats();
|
|
1173
1196
|
}
|
|
@@ -1184,50 +1207,57 @@ tool(
|
|
|
1184
1207
|
}
|
|
1185
1208
|
|
|
1186
1209
|
function approveTestCases() {
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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
|
+
})
|
|
1207
1232
|
})
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
1216
|
-
|
|
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 => {
|
|
1217
1245
|
showNotification('Error approving test cases', 'error');
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
showNotification('Error
|
|
1246
|
+
console.error('Error:', error);
|
|
1247
|
+
});
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
showNotification('Error processing test cases', 'error');
|
|
1222
1250
|
console.error('Error:', error);
|
|
1223
|
-
}
|
|
1251
|
+
}
|
|
1224
1252
|
}
|
|
1225
1253
|
|
|
1226
|
-
// Update test cases when
|
|
1254
|
+
// Update test cases when textarea changes
|
|
1227
1255
|
document.addEventListener('input', function(e) {
|
|
1228
|
-
if (e.target.tagName === '
|
|
1256
|
+
if (e.target.tagName === 'TEXTAREA') {
|
|
1229
1257
|
const index = parseInt(e.target.getAttribute('data-index'));
|
|
1230
|
-
testCases[index]
|
|
1258
|
+
if (!isNaN(index) && testCases[index]) {
|
|
1259
|
+
testCases[index][0] = e.target.value;
|
|
1260
|
+
}
|
|
1231
1261
|
}
|
|
1232
1262
|
});
|
|
1233
1263
|
</script>
|
|
@@ -1236,10 +1266,15 @@ tool(
|
|
|
1236
1266
|
`);
|
|
1237
1267
|
});
|
|
1238
1268
|
|
|
1239
|
-
// Approval endpoint
|
|
1269
|
+
// Approval endpoint with better error handling
|
|
1240
1270
|
app.post('/approve', (req, res) => {
|
|
1241
1271
|
try {
|
|
1242
|
-
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
|
+
|
|
1243
1278
|
finalTestCases = approvedTestCases;
|
|
1244
1279
|
approvalStatus = 'approved';
|
|
1245
1280
|
|
|
@@ -1258,6 +1293,7 @@ tool(
|
|
|
1258
1293
|
server.close();
|
|
1259
1294
|
}, 3000);
|
|
1260
1295
|
} catch (error) {
|
|
1296
|
+
console.error('Approval error:', error);
|
|
1261
1297
|
res.status(500).json({ success: false, message: error.message });
|
|
1262
1298
|
}
|
|
1263
1299
|
});
|
|
@@ -1267,8 +1303,26 @@ tool(
|
|
|
1267
1303
|
console.log(`Test case review server running at http://localhost:${port}`);
|
|
1268
1304
|
});
|
|
1269
1305
|
|
|
1270
|
-
//
|
|
1271
|
-
|
|
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
|
+
}
|
|
1272
1326
|
|
|
1273
1327
|
// Store session globally for status checking
|
|
1274
1328
|
global.approvalSessions = global.approvalSessions || {};
|
|
@@ -1278,15 +1332,18 @@ tool(
|
|
|
1278
1332
|
timestamp: Date.now()
|
|
1279
1333
|
};
|
|
1280
1334
|
|
|
1281
|
-
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.'}`;
|
|
1282
1338
|
|
|
1283
1339
|
} catch (err) {
|
|
1340
|
+
console.error('Review tool error:', err);
|
|
1284
1341
|
return `❌ Error starting test case review: ${err.message}`;
|
|
1285
1342
|
}
|
|
1286
1343
|
}
|
|
1287
1344
|
);
|
|
1288
1345
|
|
|
1289
|
-
//
|
|
1346
|
+
// Fix 3: Updated check_approval_status tool with better error handling
|
|
1290
1347
|
tool(
|
|
1291
1348
|
"check_approval_status",
|
|
1292
1349
|
"Check the approval status of test cases review session (waits 25 seconds before checking)",
|
|
@@ -1313,25 +1370,28 @@ tool(
|
|
|
1313
1370
|
sessionId: sessionId
|
|
1314
1371
|
};
|
|
1315
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
|
+
|
|
1316
1380
|
// Clean up session after returning result
|
|
1317
1381
|
delete global.approvalSessions[sessionId];
|
|
1318
1382
|
|
|
1319
|
-
return `✅ Test cases approved successfully!\n\nApproved ${result.approvedCount} test cases:\n\n${
|
|
1320
|
-
result.testCases.map((tc, index) =>
|
|
1321
|
-
`${index + 1}. ${tc[0]}${tc[2] ? ` (${tc[2]})` : ''}`
|
|
1322
|
-
).join('\n')
|
|
1323
|
-
}\n\nSession ID: ${sessionId}`;
|
|
1383
|
+
return `✅ Test cases approved successfully!\n\nApproved ${result.approvedCount} test cases:\n\n${formattedTestCases}\n\nSession completed: ${sessionId}`;
|
|
1324
1384
|
} else {
|
|
1325
1385
|
return "⏳ Still waiting for approval. The review session is active but not yet approved. Please complete the review in the browser.";
|
|
1326
1386
|
}
|
|
1327
1387
|
|
|
1328
1388
|
} catch (err) {
|
|
1389
|
+
console.error('Check approval status error:', err);
|
|
1329
1390
|
return `❌ Error checking approval status: ${err.message}`;
|
|
1330
1391
|
}
|
|
1331
1392
|
}
|
|
1332
1393
|
);
|
|
1333
1394
|
|
|
1334
|
-
|
|
1335
1395
|
return server;
|
|
1336
1396
|
};
|
|
1337
1397
|
|