@nbakka/mcp-appium 2.0.99 → 3.0.0

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 CHANGED
@@ -794,276 +794,544 @@ tool(
794
794
  }
795
795
  );
796
796
 
797
- // Global state for tracking approval sessions
798
- let approvalSessions = new Map();
799
-
797
+ // Tool 1: Review Test Cases - Launch HTML UI for approval
800
798
  tool(
801
799
  "review_testcases",
802
800
  "Open test cases in browser for manual approval.",
803
801
  {
804
- testCases: zod_1.z
805
- .array(zod_1.z.array(zod_1.z.string()))
806
- .describe("test cases array generated by tool generate_testcases_from_ticket_data")
802
+ testCases: zod_1.z.array(zod_1.z.array(zod_1.z.string())).describe("test cases array generated by tool generate_testcases_from_ticket_data")
807
803
  },
808
804
  async ({ testCases }) => {
809
805
  try {
810
- const sessionId = Date.now().toString();
811
-
812
- function parseTestCases(testCasesArray) {
813
- const parsed = { new: [], modify: [], remove: [] };
814
- testCasesArray.forEach(tc => {
815
- if (tc.length === 4 && tc[2] === 'Modify') {
816
- parsed.modify.push({ id: tc[3], original: tc[0], modified: tc[1] });
817
- } else if (tc.length === 3 && tc[1] === 'Remove') {
818
- parsed.remove.push({ id: tc[2], description: tc[0] });
819
- } else if (tc.length === 2 && tc[1] === 'New') {
820
- parsed.new.push({
821
- description: tc[0],
822
- id: `NEW-${Date.now()}-${Math.random().toString(36).slice(2)}`
823
- });
824
- }
825
- });
826
- return parsed;
806
+ const express = require('express');
807
+ const open = require('open');
808
+ const app = express();
809
+ const port = 3001;
810
+
811
+ // Generate unique session ID
812
+ const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
813
+
814
+ // Store approval status
815
+ let approvalStatus = 'pending';
816
+ let finalTestCases = JSON.parse(JSON.stringify(testCases)); // Deep copy
817
+
818
+ // Serve static files from review-ui folder if it exists
819
+ const reviewUiPath = path.join(__dirname, 'review-ui');
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
827
825
  }
828
826
 
829
- const parsedTestCases = parseTestCases(testCases);
827
+ app.use(express.json());
828
+ app.use(express.static('public'));
829
+
830
+ // Main review page
831
+ app.get('/', (req, res) => {
832
+ res.send(`
833
+ <!DOCTYPE html>
834
+ <html lang="en">
835
+ <head>
836
+ <meta charset="UTF-8">
837
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
838
+ <title>Test Cases Review & Approval</title>
839
+ <style>
840
+ * {
841
+ margin: 0;
842
+ padding: 0;
843
+ box-sizing: border-box;
844
+ }
830
845
 
831
- // Initialize session with proper locking mechanism
832
- approvalSessions.set(sessionId, {
833
- status: 'pending',
834
- testCases: parsedTestCases,
835
- originalTestCases: JSON.parse(JSON.stringify(parsedTestCases)),
836
- server: null,
837
- startTime: Date.now(),
838
- locked: false
839
- });
846
+ body {
847
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
848
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
849
+ min-height: 100vh;
850
+ padding: 20px;
851
+ }
840
852
 
841
- const app = express();
853
+ .container {
854
+ max-width: 1200px;
855
+ margin: 0 auto;
856
+ background: white;
857
+ border-radius: 15px;
858
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
859
+ overflow: hidden;
860
+ }
842
861
 
843
- // Try multiple ports if 3001 is busy
844
- let port = 3001;
845
- let server = null;
846
-
847
- const tryStartServer = (portToTry) => {
848
- return new Promise((resolve, reject) => {
849
- const testServer = app.listen(portToTry, () => {
850
- resolve({ server: testServer, port: portToTry });
851
- }).on('error', (err) => {
852
- if (err.code === 'EADDRINUSE') {
853
- reject(err);
854
- } else {
855
- reject(err);
856
- }
857
- });
858
- });
859
- };
862
+ .header {
863
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
864
+ color: white;
865
+ padding: 30px;
866
+ text-align: center;
867
+ }
860
868
 
861
- // Try ports 3001-3010
862
- for (let i = 0; i < 10; i++) {
863
- try {
864
- const result = await tryStartServer(port + i);
865
- server = result.server;
866
- port = result.port;
867
- break;
868
- } catch (err) {
869
- if (i === 9) throw new Error(`No available ports found (tried ${port}-${port + 9})`);
869
+ .header h1 {
870
+ font-size: 2.5rem;
871
+ margin-bottom: 10px;
872
+ font-weight: 700;
870
873
  }
871
- }
872
874
 
873
- app.use(express.json());
874
- app.use(express.static(path.join(__dirname, 'review-ui')));
875
+ .header p {
876
+ font-size: 1.1rem;
877
+ opacity: 0.9;
878
+ }
875
879
 
876
- app.get("/", (_req, res) =>
877
- res.sendFile(path.join(__dirname, 'review-ui', 'index.html'))
878
- );
880
+ .stats {
881
+ display: flex;
882
+ justify-content: space-around;
883
+ background: #f8f9fa;
884
+ padding: 20px;
885
+ border-bottom: 1px solid #e9ecef;
886
+ }
879
887
 
880
- app.get("/api/testcases", (_req, res) => {
881
- const session = approvalSessions.get(sessionId);
882
- if (!session) return res.status(404).json({ error: 'Session not found' });
883
- res.json({ sessionId, testCases: session.testCases });
884
- });
888
+ .stat-item {
889
+ text-align: center;
890
+ }
885
891
 
886
- app.post(`/delete/${sessionId}`, (req, res) => {
887
- const session = approvalSessions.get(sessionId);
888
- if (!session) return res.status(404).json({ error: 'Session not found' });
892
+ .stat-number {
893
+ font-size: 2rem;
894
+ font-weight: bold;
895
+ color: #495057;
896
+ }
889
897
 
890
- const { type, id } = req.body;
891
- if (!['new', 'modify', 'remove'].includes(type) || !id) {
892
- return res.status(400).json({ error: 'Invalid delete request' });
898
+ .stat-label {
899
+ color: #6c757d;
900
+ margin-top: 5px;
893
901
  }
894
- try {
895
- if (session.testCases[type] && Array.isArray(session.testCases[type])) {
896
- session.testCases[type] = session.testCases[type].filter(tc => tc.id !== id);
897
- approvalSessions.set(sessionId, session);
898
- return res.json({ status: 'deleted', testCases: session.testCases });
899
- } else {
900
- return res.status(400).json({ error: `Invalid type: ${type}` });
901
- }
902
- } catch (error) {
903
- console.error('Delete error:', error);
904
- return res.status(500).json({ error: 'Failed to delete test case' });
902
+
903
+ .content {
904
+ padding: 30px;
905
905
  }
906
- });
907
906
 
908
- app.post(`/update/${sessionId}`, (req, res) => {
909
- const session = approvalSessions.get(sessionId);
910
- if (!session) return res.status(404).json({ error: 'Session not found' });
907
+ .test-case {
908
+ background: #fff;
909
+ border: 2px solid #e9ecef;
910
+ border-radius: 12px;
911
+ margin-bottom: 20px;
912
+ overflow: hidden;
913
+ transition: all 0.3s ease;
914
+ position: relative;
915
+ }
911
916
 
912
- try {
913
- const { type, id, data } = req.body;
914
- if (!['new', 'modify'].includes(type) || !id || !data) {
915
- return res.status(400).json({ error: 'Invalid update request' });
916
- }
917
+ .test-case:hover {
918
+ border-color: #4facfe;
919
+ box-shadow: 0 8px 25px rgba(79, 172, 254, 0.15);
920
+ }
917
921
 
918
- const items = session.testCases[type];
919
- const index = items.findIndex(tc => tc.id === id);
920
- if (index === -1) {
921
- return res.status(404).json({ error: 'Test case not found' });
922
- }
922
+ .test-case.deleted {
923
+ opacity: 0.5;
924
+ background: #f8f9fa;
925
+ border-color: #dc3545;
926
+ }
923
927
 
924
- // Update the test case
925
- if (type === 'new') {
926
- items[index].description = data.description;
927
- } else if (type === 'modify') {
928
- items[index].modified = data.modified;
929
- }
928
+ .test-case-header {
929
+ background: #f8f9fa;
930
+ padding: 15px 20px;
931
+ border-bottom: 1px solid #e9ecef;
932
+ display: flex;
933
+ justify-content: between;
934
+ align-items: center;
935
+ }
930
936
 
931
- approvalSessions.set(sessionId, session);
932
- return res.json({ status: 'updated', testCases: session.testCases });
933
- } catch (error) {
934
- console.error('Update error:', error);
935
- return res.status(500).json({ error: 'Failed to update test case' });
937
+ .test-case-id {
938
+ font-weight: 600;
939
+ color: #495057;
936
940
  }
937
- });
938
941
 
939
- app.post(`/approve/${sessionId}`, (req, res) => {
940
- const session = approvalSessions.get(sessionId);
941
- if (!session) return res.status(404).json({ error: 'Session not found' });
942
+ .test-case-status {
943
+ padding: 5px 12px;
944
+ border-radius: 20px;
945
+ font-size: 0.85rem;
946
+ font-weight: 500;
947
+ text-transform: uppercase;
948
+ }
942
949
 
943
- try {
944
- // Use atomic update with locking
945
- session.locked = true;
946
- session.testCases = req.body;
947
- session.finalTestCases = JSON.parse(JSON.stringify(req.body)); // Deep copy
948
- session.status = 'approved';
949
- session.approvedAt = Date.now();
950
- approvalSessions.set(sessionId, session);
951
-
952
- console.log(`Session ${sessionId} approved at ${new Date().toISOString()}`);
953
- res.json({ status: 'approved', message: 'Test cases approved successfully!' });
954
- } catch (error) {
955
- console.error('Approval error:', error);
956
- res.status(500).json({ error: 'Failed to approve test cases' });
950
+ .status-new {
951
+ background: #d4edda;
952
+ color: #155724;
953
+ }
954
+
955
+ .status-modify {
956
+ background: #fff3cd;
957
+ color: #856404;
958
+ }
959
+
960
+ .status-remove {
961
+ background: #f8d7da;
962
+ color: #721c24;
963
+ }
964
+
965
+ .test-case-content {
966
+ padding: 20px;
967
+ }
968
+
969
+ .test-case-title {
970
+ font-size: 1.1rem;
971
+ font-weight: 500;
972
+ color: #212529;
973
+ margin-bottom: 15px;
974
+ line-height: 1.5;
975
+ }
976
+
977
+ .test-case-title input {
978
+ width: 100%;
979
+ border: 1px solid #ced4da;
980
+ border-radius: 6px;
981
+ padding: 10px;
982
+ font-size: 1rem;
983
+ transition: border-color 0.3s ease;
984
+ }
985
+
986
+ .test-case-title input:focus {
987
+ outline: none;
988
+ border-color: #4facfe;
989
+ box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
990
+ }
991
+
992
+ .test-case-actions {
993
+ display: flex;
994
+ gap: 10px;
995
+ }
996
+
997
+ .btn {
998
+ padding: 8px 16px;
999
+ border: none;
1000
+ border-radius: 6px;
1001
+ cursor: pointer;
1002
+ font-size: 0.9rem;
1003
+ transition: all 0.3s ease;
1004
+ font-weight: 500;
1005
+ }
1006
+
1007
+ .btn-delete {
1008
+ background: #dc3545;
1009
+ color: white;
1010
+ }
1011
+
1012
+ .btn-delete:hover {
1013
+ background: #c82333;
1014
+ }
1015
+
1016
+ .btn-restore {
1017
+ background: #28a745;
1018
+ color: white;
1019
+ }
1020
+
1021
+ .btn-restore:hover {
1022
+ background: #218838;
1023
+ }
1024
+
1025
+ .footer {
1026
+ background: #f8f9fa;
1027
+ padding: 30px;
1028
+ text-align: center;
1029
+ border-top: 1px solid #e9ecef;
1030
+ }
1031
+
1032
+ .btn-primary {
1033
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
1034
+ color: white;
1035
+ padding: 15px 40px;
1036
+ font-size: 1.1rem;
1037
+ border: none;
1038
+ border-radius: 50px;
1039
+ cursor: pointer;
1040
+ transition: all 0.3s ease;
1041
+ margin: 0 10px;
1042
+ }
1043
+
1044
+ .btn-primary:hover {
1045
+ transform: translateY(-2px);
1046
+ box-shadow: 0 10px 30px rgba(79, 172, 254, 0.4);
1047
+ }
1048
+
1049
+ .btn-secondary {
1050
+ background: #6c757d;
1051
+ color: white;
1052
+ padding: 15px 40px;
1053
+ font-size: 1.1rem;
1054
+ border: none;
1055
+ border-radius: 50px;
1056
+ cursor: pointer;
1057
+ transition: all 0.3s ease;
1058
+ margin: 0 10px;
1059
+ }
1060
+
1061
+ .btn-secondary:hover {
1062
+ background: #5a6268;
1063
+ transform: translateY(-2px);
1064
+ }
1065
+
1066
+ .notification {
1067
+ position: fixed;
1068
+ top: 20px;
1069
+ right: 20px;
1070
+ padding: 15px 25px;
1071
+ background: #28a745;
1072
+ color: white;
1073
+ border-radius: 8px;
1074
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
1075
+ display: none;
1076
+ z-index: 1000;
1077
+ }
1078
+ </style>
1079
+ </head>
1080
+ <body>
1081
+ <div class="container">
1082
+ <div class="header">
1083
+ <h1>Test Cases Review</h1>
1084
+ <p>Review, edit, and approve test cases for JIRA ticket</p>
1085
+ </div>
1086
+
1087
+ <div class="stats">
1088
+ <div class="stat-item">
1089
+ <div class="stat-number" id="totalCount">${testCases.length}</div>
1090
+ <div class="stat-label">Total Test Cases</div>
1091
+ </div>
1092
+ <div class="stat-item">
1093
+ <div class="stat-number" id="activeCount">${testCases.length}</div>
1094
+ <div class="stat-label">Active</div>
1095
+ </div>
1096
+ <div class="stat-item">
1097
+ <div class="stat-number" id="deletedCount">0</div>
1098
+ <div class="stat-label">Deleted</div>
1099
+ </div>
1100
+ </div>
1101
+
1102
+ <div class="content">
1103
+ <div id="testCases">
1104
+ ${testCases.map((testCase, index) => `
1105
+ <div class="test-case" data-index="${index}">
1106
+ <div class="test-case-header">
1107
+ <span class="test-case-id">Test Case #${index + 1}</span>
1108
+ <span class="test-case-status status-${testCase[2] ? testCase[2].toLowerCase() : 'new'}">${testCase[2] || 'New'}</span>
1109
+ </div>
1110
+ <div class="test-case-content">
1111
+ <div class="test-case-title">
1112
+ <input type="text" value="${testCase[0]}" data-index="${index}">
1113
+ </div>
1114
+ <div class="test-case-actions">
1115
+ <button class="btn btn-delete" onclick="toggleDelete(${index})">Delete</button>
1116
+ </div>
1117
+ </div>
1118
+ </div>
1119
+ `).join('')}
1120
+ </div>
1121
+ </div>
1122
+
1123
+ <div class="footer">
1124
+ <button class="btn-primary" onclick="approveTestCases()">Approve Test Cases</button>
1125
+ <button class="btn-secondary" onclick="resetAll()">Reset All</button>
1126
+ </div>
1127
+ </div>
1128
+
1129
+ <div class="notification" id="notification"></div>
1130
+
1131
+ <script>
1132
+ let testCases = ${JSON.stringify(testCases)};
1133
+ let deletedIndices = new Set();
1134
+
1135
+ function updateStats() {
1136
+ document.getElementById('activeCount').textContent = testCases.length - deletedIndices.size;
1137
+ document.getElementById('deletedCount').textContent = deletedIndices.size;
1138
+ }
1139
+
1140
+ function toggleDelete(index) {
1141
+ const testCaseEl = document.querySelector(\`[data-index="\${index}"]\`);
1142
+ const btn = testCaseEl.querySelector('.btn-delete');
1143
+
1144
+ if (deletedIndices.has(index)) {
1145
+ // Restore
1146
+ deletedIndices.delete(index);
1147
+ testCaseEl.classList.remove('deleted');
1148
+ btn.textContent = 'Delete';
1149
+ btn.className = 'btn btn-delete';
1150
+ } else {
1151
+ // Delete
1152
+ deletedIndices.add(index);
1153
+ testCaseEl.classList.add('deleted');
1154
+ btn.textContent = 'Restore';
1155
+ btn.className = 'btn btn-restore';
1156
+ }
1157
+ updateStats();
1158
+ }
1159
+
1160
+ function resetAll() {
1161
+ deletedIndices.clear();
1162
+ document.querySelectorAll('.test-case').forEach((el, index) => {
1163
+ el.classList.remove('deleted');
1164
+ const btn = el.querySelector('.btn-delete, .btn-restore');
1165
+ btn.textContent = 'Delete';
1166
+ btn.className = 'btn btn-delete';
1167
+
1168
+ // Reset input value
1169
+ const input = el.querySelector('input');
1170
+ input.value = testCases[index][0];
1171
+ });
1172
+ updateStats();
1173
+ }
1174
+
1175
+ function showNotification(message, type = 'success') {
1176
+ const notification = document.getElementById('notification');
1177
+ notification.textContent = message;
1178
+ notification.style.background = type === 'success' ? '#28a745' : '#dc3545';
1179
+ notification.style.display = 'block';
1180
+
1181
+ setTimeout(() => {
1182
+ notification.style.display = 'none';
1183
+ }, 3000);
1184
+ }
1185
+
1186
+ function approveTestCases() {
1187
+ // Collect updated test cases
1188
+ const updatedTestCases = [];
1189
+ document.querySelectorAll('.test-case').forEach((el, index) => {
1190
+ if (!deletedIndices.has(index)) {
1191
+ const input = el.querySelector('input');
1192
+ const originalTestCase = [...testCases[index]];
1193
+ originalTestCase[0] = input.value;
1194
+ updatedTestCases.push(originalTestCase);
1195
+ }
1196
+ });
1197
+
1198
+ // Send approval to server
1199
+ fetch('/approve', {
1200
+ method: 'POST',
1201
+ headers: {
1202
+ 'Content-Type': 'application/json',
1203
+ },
1204
+ body: JSON.stringify({
1205
+ sessionId: '${sessionId}',
1206
+ testCases: updatedTestCases
1207
+ })
1208
+ })
1209
+ .then(response => response.json())
1210
+ .then(data => {
1211
+ if (data.success) {
1212
+ showNotification('Test cases approved successfully!');
1213
+ setTimeout(() => {
1214
+ window.close();
1215
+ }, 2000);
1216
+ } else {
1217
+ showNotification('Error approving test cases', 'error');
1218
+ }
1219
+ })
1220
+ .catch(error => {
1221
+ showNotification('Error approving test cases', 'error');
1222
+ console.error('Error:', error);
1223
+ });
957
1224
  }
1225
+
1226
+ // Update test cases when input changes
1227
+ document.addEventListener('input', function(e) {
1228
+ if (e.target.tagName === 'INPUT') {
1229
+ const index = parseInt(e.target.getAttribute('data-index'));
1230
+ testCases[index][0] = e.target.value;
1231
+ }
1232
+ });
1233
+ </script>
1234
+ </body>
1235
+ </html>
1236
+ `);
958
1237
  });
959
1238
 
960
- app.post(`/cancel/${sessionId}`, (_req, res) => {
961
- const session = approvalSessions.get(sessionId);
962
- if (session) {
963
- session.status = 'cancelled';
964
- session.cancelledAt = Date.now();
965
- approvalSessions.set(sessionId, session);
966
- console.log(`Session ${sessionId} cancelled at ${new Date().toISOString()}`);
1239
+ // Approval endpoint
1240
+ app.post('/approve', (req, res) => {
1241
+ try {
1242
+ const { testCases: approvedTestCases } = req.body;
1243
+ finalTestCases = approvedTestCases;
1244
+ approvalStatus = 'approved';
1245
+
1246
+ // Save to global state for the check tool
1247
+ global.approvalSessions = global.approvalSessions || {};
1248
+ global.approvalSessions[sessionId] = {
1249
+ status: 'approved',
1250
+ testCases: finalTestCases,
1251
+ timestamp: Date.now()
1252
+ };
1253
+
1254
+ res.json({ success: true, message: 'Test cases approved successfully' });
1255
+
1256
+ // Close server after approval
1257
+ setTimeout(() => {
1258
+ server.close();
1259
+ }, 3000);
1260
+ } catch (error) {
1261
+ res.status(500).json({ success: false, message: error.message });
967
1262
  }
968
- res.json({ status: 'cancelled', message: 'Review cancelled' });
969
1263
  });
970
1264
 
971
- // Update session with server reference
972
- const stored = approvalSessions.get(sessionId);
973
- stored.server = server;
974
- approvalSessions.set(sessionId, stored);
1265
+ // Start server
1266
+ const server = app.listen(port, () => {
1267
+ console.log(`Test case review server running at http://localhost:${port}`);
1268
+ });
975
1269
 
976
1270
  // Open browser
977
- try {
978
- const { default: open } = await import('open');
979
- await open(`http://localhost:${port}`);
980
- } catch { /* silent */ }
981
-
982
- // Timeout safeguard - but don't override approved status
983
- setTimeout(() => {
984
- const s = approvalSessions.get(sessionId);
985
- if (s && s.status === 'pending') {
986
- s.status = 'timeout';
987
- s.server?.close();
988
- approvalSessions.set(sessionId, s);
989
- }
990
- }, 300000);
1271
+ await open(`http://localhost:${port}`);
991
1272
 
992
- return `✅ Test case review interface opened successfully!
993
- Session ID: ${sessionId}
994
- Test Cases Count: ${testCases.length}
995
- Browser URL: http://localhost:${port}
996
- Status: review_started
1273
+ // Store session globally for status checking
1274
+ global.approvalSessions = global.approvalSessions || {};
1275
+ global.approvalSessions[sessionId] = {
1276
+ status: 'pending',
1277
+ testCases: testCases,
1278
+ timestamp: Date.now()
1279
+ };
997
1280
 
998
- Instructions: Use check_approval_status tool with session ID "${sessionId}" to poll for approval status.`;
1281
+ return `✅ Test case review session started. Session ID: ${sessionId}. Browser opened for manual review and approval.`;
999
1282
 
1000
1283
  } catch (err) {
1001
- return `❌ Error setting up review interface: ${err.message}`;
1284
+ return `❌ Error starting test case review: ${err.message}`;
1002
1285
  }
1003
1286
  }
1004
1287
  );
1005
1288
 
1289
+ // Tool 2: Check Approval Status
1006
1290
  tool(
1007
1291
  "check_approval_status",
1008
- "Check the approval status of test cases review session (waits 20 seconds before checking)",
1009
- { sessionId: zod_1.z.string().describe("Session ID from review_testcases") },
1292
+ "Check the approval status of test cases review session (waits 25 seconds before checking)",
1293
+ {
1294
+ sessionId: zod_1.z.string().describe("Session ID from review_testcases")
1295
+ },
1010
1296
  async ({ sessionId }) => {
1011
- const session = approvalSessions.get(sessionId);
1012
- if (!session) return `❌ Session not found. Invalid session ID: ${sessionId}`;
1013
-
1014
- const now = Date.now();
1015
- const elapsed = Math.floor((now - session.startTime) / 1000);
1016
-
1017
- // Check if already approved BEFORE waiting
1018
- if (session.status === 'approved') {
1019
- const approvedTestCases = session.finalTestCases || session.testCases;
1020
- session.server?.close();
1021
- approvalSessions.delete(sessionId);
1022
- return `✅ Test cases approved. Elapsed: ${elapsed}s\n${JSON.stringify(approvedTestCases, null, 2)}`;
1023
- }
1297
+ try {
1298
+ // Wait for 25 seconds
1299
+ await new Promise(resolve => setTimeout(resolve, 25000));
1024
1300
 
1025
- if (session.status === 'cancelled') {
1026
- session.server?.close();
1027
- approvalSessions.delete(sessionId);
1028
- return `❌ Review cancelled. Elapsed: ${elapsed}s`;
1029
- }
1301
+ // Check global approval sessions
1302
+ if (!global.approvalSessions || !global.approvalSessions[sessionId]) {
1303
+ return "❌ Session not found. Please ensure the review session is still active.";
1304
+ }
1030
1305
 
1031
- if (session.status === 'timeout' || elapsed > 300) {
1032
- session.server?.close();
1033
- approvalSessions.delete(sessionId);
1034
- return `⏰ Review session timed out. Elapsed: ${elapsed}s`;
1035
- }
1306
+ const session = global.approvalSessions[sessionId];
1036
1307
 
1037
- // Wait 20 seconds before checking status again
1038
- await new Promise(r => setTimeout(r, 20000));
1039
-
1040
- // Re-fetch session after wait to check for status changes
1041
- const refreshed = approvalSessions.get(sessionId);
1042
- if (!refreshed) return `❌ Session expired during wait`;
1043
-
1044
- const newElapsed = Math.floor((Date.now() - refreshed.startTime) / 1000);
1045
-
1046
- if (refreshed.status === 'approved') {
1047
- const approvedTestCases = refreshed.finalTestCases || refreshed.testCases;
1048
- refreshed.server?.close();
1049
- approvalSessions.delete(sessionId);
1050
- return `✅ Test cases approved. Elapsed: ${newElapsed}s\n${JSON.stringify(approvedTestCases, null, 2)}`;
1051
- } else if (refreshed.status === 'cancelled') {
1052
- refreshed.server?.close();
1053
- approvalSessions.delete(sessionId);
1054
- return `❌ Review cancelled. Elapsed: ${newElapsed}s`;
1055
- } else if (refreshed.status === 'timeout' || newElapsed > 300) {
1056
- refreshed.server?.close();
1057
- approvalSessions.delete(sessionId);
1058
- return `⏰ Review session timed out. Elapsed: ${newElapsed}s`;
1059
- }
1308
+ if (session.status === 'approved') {
1309
+ const result = {
1310
+ status: 'approved',
1311
+ testCases: session.testCases,
1312
+ approvedCount: session.testCases.length,
1313
+ sessionId: sessionId
1314
+ };
1315
+
1316
+ // Clean up session after returning result
1317
+ delete global.approvalSessions[sessionId];
1318
+
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}`;
1324
+ } else {
1325
+ return "⏳ Still waiting for approval. The review session is active but not yet approved. Please complete the review in the browser.";
1326
+ }
1060
1327
 
1061
- return `⏳ Still pending. Elapsed: ${newElapsed}s Remaining: ${Math.max(0, 300 - newElapsed)}s`;
1328
+ } catch (err) {
1329
+ return `❌ Error checking approval status: ${err.message}`;
1330
+ }
1062
1331
  }
1063
1332
  );
1064
1333
 
1065
1334
 
1066
-
1067
1335
  return server;
1068
1336
  };
1069
1337