@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.
- package/lib/server.js +134 -48
- 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
|
-
|
|
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
|
-
//
|
|
820
|
-
|
|
821
|
-
//
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
995
|
-
background: #
|
|
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">${
|
|
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">${
|
|
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
|
-
${
|
|
1115
|
-
|
|
1138
|
+
${processedTestCases.map((testCase, index) => {
|
|
1139
|
+
// Safely escape HTML characters
|
|
1140
|
+
const safeTitle = testCase.title
|
|
1141
|
+
.replace(/&/g, '&')
|
|
1142
|
+
.replace(/</g, '<')
|
|
1143
|
+
.replace(/>/g, '>')
|
|
1144
|
+
.replace(/"/g, '"')
|
|
1145
|
+
.replace(/'/g, ''');
|
|
1146
|
+
|
|
1147
|
+
const safeDescription = testCase.description
|
|
1148
|
+
.replace(/&/g, '&')
|
|
1149
|
+
.replace(/</g, '<')
|
|
1150
|
+
.replace(/>/g, '>')
|
|
1151
|
+
.replace(/"/g, '"')
|
|
1152
|
+
.replace(/'/g, ''');
|
|
1153
|
+
|
|
1154
|
+
const safeOriginal = testCase.originalCase
|
|
1155
|
+
.replace(/&/g, '&')
|
|
1156
|
+
.replace(/</g, '<')
|
|
1157
|
+
.replace(/>/g, '>')
|
|
1158
|
+
.replace(/"/g, '"')
|
|
1159
|
+
.replace(/'/g, ''');
|
|
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
|
|
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...">${
|
|
1169
|
+
<textarea data-index="${index}" placeholder="Enter test case title and steps...">${safeTitle}${safeDescription ? '\n\nSteps:\n' + safeDescription : ''}</textarea>
|
|
1125
1170
|
</div>
|
|
1126
|
-
${testCase
|
|
1171
|
+
${testCase.status === 'Modify' && testCase.originalCase ? `
|
|
1127
1172
|
<div class="original-case">
|
|
1128
|
-
<
|
|
1173
|
+
<strong>Original Test Case:</strong><br>
|
|
1174
|
+
${safeOriginal}
|
|
1129
1175
|
</div>
|
|
1130
1176
|
` : ''}
|
|
1131
|
-
${testCase
|
|
1177
|
+
${testCase.status === 'Remove' ? `
|
|
1132
1178
|
<div class="remove-case">
|
|
1133
|
-
<
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
1217
|
-
|
|
1218
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
-
//
|
|
1302
|
-
|
|
1303
|
-
console.
|
|
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
|
-
|
|
1392
|
+
const newServer = app.listen(newPort, () => {
|
|
1312
1393
|
console.log(`Test case review server running at http://localhost:${newPort}`);
|
|
1313
|
-
|
|
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
|
|
1322
|
-
} catch (
|
|
1323
|
-
|
|
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:
|
|
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);
|