@nbakka/mcp-appium 3.0.5 → 3.0.6
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 +198 -232
- package/package.json +1 -1
package/lib/server.js
CHANGED
|
@@ -806,16 +806,33 @@ tool(
|
|
|
806
806
|
async ({ testCases }) => {
|
|
807
807
|
try {
|
|
808
808
|
const app = express();
|
|
809
|
-
|
|
809
|
+
let port = 3001;
|
|
810
|
+
|
|
811
|
+
// Find an available port
|
|
812
|
+
const findAvailablePort = async (startPort) => {
|
|
813
|
+
const net = require('net');
|
|
814
|
+
return new Promise((resolve) => {
|
|
815
|
+
const server = net.createServer();
|
|
816
|
+
server.listen(startPort, () => {
|
|
817
|
+
const port = server.address().port;
|
|
818
|
+
server.close(() => resolve(port));
|
|
819
|
+
});
|
|
820
|
+
server.on('error', () => {
|
|
821
|
+
resolve(findAvailablePort(startPort + 1));
|
|
822
|
+
});
|
|
823
|
+
});
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
port = await findAvailablePort(port);
|
|
810
827
|
|
|
811
828
|
// Generate unique session ID
|
|
812
829
|
const sessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
813
830
|
|
|
814
831
|
// Store approval status
|
|
815
832
|
let approvalStatus = 'pending';
|
|
816
|
-
let finalTestCases =
|
|
833
|
+
let finalTestCases = [];
|
|
817
834
|
|
|
818
|
-
app.use(express.json({ limit: '10mb' }));
|
|
835
|
+
app.use(express.json({ limit: '10mb' }));
|
|
819
836
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
820
837
|
|
|
821
838
|
// Process test cases - handle the specific format properly
|
|
@@ -844,7 +861,7 @@ tool(
|
|
|
844
861
|
// Main review page with proper handling
|
|
845
862
|
app.get('/', (req, res) => {
|
|
846
863
|
try {
|
|
847
|
-
|
|
864
|
+
const htmlContent = `
|
|
848
865
|
<!DOCTYPE html>
|
|
849
866
|
<html lang="en">
|
|
850
867
|
<head>
|
|
@@ -912,189 +929,154 @@ tool(
|
|
|
912
929
|
|
|
913
930
|
.stat-label {
|
|
914
931
|
color: #6c757d;
|
|
932
|
+
font-size: 0.9rem;
|
|
915
933
|
margin-top: 5px;
|
|
916
934
|
}
|
|
917
935
|
|
|
918
|
-
.
|
|
919
|
-
padding:
|
|
920
|
-
max-height: 70vh;
|
|
921
|
-
overflow-y: auto;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
.test-case {
|
|
925
|
-
background: #fff;
|
|
926
|
-
border: 2px solid #e9ecef;
|
|
927
|
-
border-radius: 12px;
|
|
928
|
-
margin-bottom: 20px;
|
|
929
|
-
overflow: hidden;
|
|
930
|
-
transition: all 0.3s ease;
|
|
931
|
-
position: relative;
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
.test-case:hover {
|
|
935
|
-
border-color: #4facfe;
|
|
936
|
-
box-shadow: 0 8px 25px rgba(79, 172, 254, 0.15);
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
.test-case.deleted {
|
|
940
|
-
opacity: 0.5;
|
|
941
|
-
background: #f8f9fa;
|
|
942
|
-
border-color: #dc3545;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
.test-case-header {
|
|
936
|
+
.controls {
|
|
937
|
+
padding: 20px;
|
|
946
938
|
background: #f8f9fa;
|
|
947
|
-
padding: 15px 20px;
|
|
948
|
-
border-bottom: 1px solid #e9ecef;
|
|
949
939
|
display: flex;
|
|
950
940
|
justify-content: space-between;
|
|
951
941
|
align-items: center;
|
|
942
|
+
flex-wrap: wrap;
|
|
943
|
+
gap: 10px;
|
|
952
944
|
}
|
|
953
945
|
|
|
954
|
-
.
|
|
946
|
+
.btn {
|
|
947
|
+
padding: 12px 24px;
|
|
948
|
+
border: none;
|
|
949
|
+
border-radius: 8px;
|
|
955
950
|
font-weight: 600;
|
|
956
|
-
|
|
951
|
+
cursor: pointer;
|
|
952
|
+
transition: all 0.3s ease;
|
|
953
|
+
text-decoration: none;
|
|
954
|
+
display: inline-block;
|
|
955
|
+
font-size: 14px;
|
|
957
956
|
}
|
|
958
957
|
|
|
959
|
-
.
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
font-size: 0.85rem;
|
|
963
|
-
font-weight: 500;
|
|
964
|
-
text-transform: uppercase;
|
|
958
|
+
.btn-primary {
|
|
959
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
960
|
+
color: white;
|
|
965
961
|
}
|
|
966
962
|
|
|
967
|
-
.
|
|
968
|
-
|
|
969
|
-
|
|
963
|
+
.btn-primary:hover {
|
|
964
|
+
transform: translateY(-2px);
|
|
965
|
+
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
970
966
|
}
|
|
971
967
|
|
|
972
|
-
.
|
|
973
|
-
background: #
|
|
974
|
-
color:
|
|
968
|
+
.btn-secondary {
|
|
969
|
+
background: #6c757d;
|
|
970
|
+
color: white;
|
|
975
971
|
}
|
|
976
972
|
|
|
977
|
-
.
|
|
978
|
-
background: #
|
|
979
|
-
color: #721c24;
|
|
973
|
+
.btn-secondary:hover {
|
|
974
|
+
background: #5a6268;
|
|
980
975
|
}
|
|
981
976
|
|
|
982
|
-
.
|
|
983
|
-
|
|
977
|
+
.btn-delete {
|
|
978
|
+
background: #dc3545;
|
|
979
|
+
color: white;
|
|
980
|
+
padding: 8px 16px;
|
|
981
|
+
font-size: 12px;
|
|
984
982
|
}
|
|
985
983
|
|
|
986
|
-
.
|
|
987
|
-
|
|
984
|
+
.btn-delete:hover {
|
|
985
|
+
background: #c82333;
|
|
988
986
|
}
|
|
989
987
|
|
|
990
|
-
.
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
font-size: 1rem;
|
|
996
|
-
transition: border-color 0.3s ease;
|
|
997
|
-
min-height: 120px;
|
|
998
|
-
resize: vertical;
|
|
999
|
-
font-family: inherit;
|
|
988
|
+
.btn-restore {
|
|
989
|
+
background: #28a745;
|
|
990
|
+
color: white;
|
|
991
|
+
padding: 8px 16px;
|
|
992
|
+
font-size: 12px;
|
|
1000
993
|
}
|
|
1001
994
|
|
|
1002
|
-
.
|
|
1003
|
-
|
|
1004
|
-
border-color: #4facfe;
|
|
1005
|
-
box-shadow: 0 0 0 3px rgba(79, 172, 254, 0.1);
|
|
995
|
+
.btn-restore:hover {
|
|
996
|
+
background: #218838;
|
|
1006
997
|
}
|
|
1007
998
|
|
|
1008
|
-
.
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
padding: 10px;
|
|
1012
|
-
border-radius: 6px;
|
|
1013
|
-
margin-bottom: 15px;
|
|
1014
|
-
font-size: 0.9rem;
|
|
999
|
+
.test-cases {
|
|
1000
|
+
max-height: 70vh;
|
|
1001
|
+
overflow-y: auto;
|
|
1015
1002
|
}
|
|
1016
1003
|
|
|
1017
|
-
.
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
border-radius: 6px;
|
|
1022
|
-
margin-bottom: 15px;
|
|
1023
|
-
font-size: 0.9rem;
|
|
1004
|
+
.test-case {
|
|
1005
|
+
border-bottom: 1px solid #e9ecef;
|
|
1006
|
+
padding: 20px;
|
|
1007
|
+
transition: all 0.3s ease;
|
|
1024
1008
|
}
|
|
1025
1009
|
|
|
1026
|
-
.test-case
|
|
1027
|
-
|
|
1028
|
-
gap: 10px;
|
|
1010
|
+
.test-case:hover {
|
|
1011
|
+
background: #f8f9fa;
|
|
1029
1012
|
}
|
|
1030
1013
|
|
|
1031
|
-
.
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
border-radius: 6px;
|
|
1035
|
-
cursor: pointer;
|
|
1036
|
-
font-size: 0.9rem;
|
|
1037
|
-
transition: all 0.3s ease;
|
|
1038
|
-
font-weight: 500;
|
|
1014
|
+
.test-case.deleted {
|
|
1015
|
+
opacity: 0.5;
|
|
1016
|
+
background: #f8d7da;
|
|
1039
1017
|
}
|
|
1040
1018
|
|
|
1041
|
-
.
|
|
1042
|
-
|
|
1043
|
-
|
|
1019
|
+
.test-case-header {
|
|
1020
|
+
display: flex;
|
|
1021
|
+
justify-content: space-between;
|
|
1022
|
+
align-items: center;
|
|
1023
|
+
margin-bottom: 15px;
|
|
1044
1024
|
}
|
|
1045
1025
|
|
|
1046
|
-
.
|
|
1047
|
-
|
|
1026
|
+
.test-case-meta {
|
|
1027
|
+
display: flex;
|
|
1028
|
+
gap: 15px;
|
|
1029
|
+
align-items: center;
|
|
1048
1030
|
}
|
|
1049
1031
|
|
|
1050
|
-
.
|
|
1051
|
-
background: #
|
|
1032
|
+
.test-case-index {
|
|
1033
|
+
background: #007bff;
|
|
1052
1034
|
color: white;
|
|
1035
|
+
padding: 4px 8px;
|
|
1036
|
+
border-radius: 4px;
|
|
1037
|
+
font-size: 12px;
|
|
1038
|
+
font-weight: bold;
|
|
1053
1039
|
}
|
|
1054
1040
|
|
|
1055
|
-
.
|
|
1056
|
-
|
|
1041
|
+
.test-case-status {
|
|
1042
|
+
padding: 4px 12px;
|
|
1043
|
+
border-radius: 12px;
|
|
1044
|
+
font-size: 12px;
|
|
1045
|
+
font-weight: 600;
|
|
1057
1046
|
}
|
|
1058
1047
|
|
|
1059
|
-
.
|
|
1060
|
-
background: #
|
|
1061
|
-
|
|
1062
|
-
text-align: center;
|
|
1063
|
-
border-top: 1px solid #e9ecef;
|
|
1048
|
+
.status-new {
|
|
1049
|
+
background: #d4edda;
|
|
1050
|
+
color: #155724;
|
|
1064
1051
|
}
|
|
1065
1052
|
|
|
1066
|
-
.
|
|
1067
|
-
background:
|
|
1068
|
-
color:
|
|
1069
|
-
padding: 15px 40px;
|
|
1070
|
-
font-size: 1.1rem;
|
|
1071
|
-
border: none;
|
|
1072
|
-
border-radius: 50px;
|
|
1073
|
-
cursor: pointer;
|
|
1074
|
-
transition: all 0.3s ease;
|
|
1075
|
-
margin: 0 10px;
|
|
1053
|
+
.status-modify {
|
|
1054
|
+
background: #fff3cd;
|
|
1055
|
+
color: #856404;
|
|
1076
1056
|
}
|
|
1077
1057
|
|
|
1078
|
-
.
|
|
1079
|
-
|
|
1080
|
-
|
|
1058
|
+
.status-remove {
|
|
1059
|
+
background: #f8d7da;
|
|
1060
|
+
color: #721c24;
|
|
1081
1061
|
}
|
|
1082
1062
|
|
|
1083
|
-
.
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
padding: 15px
|
|
1087
|
-
|
|
1088
|
-
border:
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1063
|
+
.test-case textarea {
|
|
1064
|
+
width: 100%;
|
|
1065
|
+
min-height: 100px;
|
|
1066
|
+
padding: 15px;
|
|
1067
|
+
border: 2px solid #e9ecef;
|
|
1068
|
+
border-radius: 8px;
|
|
1069
|
+
font-family: inherit;
|
|
1070
|
+
font-size: 14px;
|
|
1071
|
+
line-height: 1.5;
|
|
1072
|
+
resize: vertical;
|
|
1073
|
+
transition: border-color 0.3s ease;
|
|
1093
1074
|
}
|
|
1094
1075
|
|
|
1095
|
-
.
|
|
1096
|
-
|
|
1097
|
-
|
|
1076
|
+
.test-case textarea:focus {
|
|
1077
|
+
outline: none;
|
|
1078
|
+
border-color: #007bff;
|
|
1079
|
+
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
|
|
1098
1080
|
}
|
|
1099
1081
|
|
|
1100
1082
|
.notification {
|
|
@@ -1102,104 +1084,94 @@ tool(
|
|
|
1102
1084
|
top: 20px;
|
|
1103
1085
|
right: 20px;
|
|
1104
1086
|
padding: 15px 25px;
|
|
1105
|
-
background: #28a745;
|
|
1106
|
-
color: white;
|
|
1107
1087
|
border-radius: 8px;
|
|
1108
|
-
|
|
1088
|
+
color: white;
|
|
1089
|
+
font-weight: 600;
|
|
1109
1090
|
display: none;
|
|
1110
1091
|
z-index: 1000;
|
|
1092
|
+
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
@media (max-width: 768px) {
|
|
1096
|
+
.container {
|
|
1097
|
+
margin: 10px;
|
|
1098
|
+
border-radius: 10px;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.header h1 {
|
|
1102
|
+
font-size: 2rem;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
.stats {
|
|
1106
|
+
flex-direction: column;
|
|
1107
|
+
gap: 15px;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
.controls {
|
|
1111
|
+
flex-direction: column;
|
|
1112
|
+
gap: 15px;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
.test-case-header {
|
|
1116
|
+
flex-direction: column;
|
|
1117
|
+
align-items: flex-start;
|
|
1118
|
+
gap: 10px;
|
|
1119
|
+
}
|
|
1111
1120
|
}
|
|
1112
1121
|
</style>
|
|
1113
1122
|
</head>
|
|
1114
1123
|
<body>
|
|
1124
|
+
<div class="notification" id="notification"></div>
|
|
1125
|
+
|
|
1115
1126
|
<div class="container">
|
|
1116
1127
|
<div class="header">
|
|
1117
|
-
<h1
|
|
1118
|
-
<p>Review, edit, and approve test cases
|
|
1128
|
+
<h1>🔍 Test Cases Review & Approval</h1>
|
|
1129
|
+
<p>Review, edit, and approve your test cases. Make any necessary changes before final approval.</p>
|
|
1119
1130
|
</div>
|
|
1120
1131
|
|
|
1121
1132
|
<div class="stats">
|
|
1122
1133
|
<div class="stat-item">
|
|
1123
1134
|
<div class="stat-number" id="totalCount">${processedTestCases.length}</div>
|
|
1124
|
-
<div class="stat-label">Total
|
|
1135
|
+
<div class="stat-label">Total Cases</div>
|
|
1125
1136
|
</div>
|
|
1126
1137
|
<div class="stat-item">
|
|
1127
1138
|
<div class="stat-number" id="activeCount">${processedTestCases.length}</div>
|
|
1128
|
-
<div class="stat-label">Active</div>
|
|
1139
|
+
<div class="stat-label">Active Cases</div>
|
|
1129
1140
|
</div>
|
|
1130
1141
|
<div class="stat-item">
|
|
1131
1142
|
<div class="stat-number" id="deletedCount">0</div>
|
|
1132
|
-
<div class="stat-label">Deleted</div>
|
|
1143
|
+
<div class="stat-label">Deleted Cases</div>
|
|
1133
1144
|
</div>
|
|
1134
1145
|
</div>
|
|
1135
1146
|
|
|
1136
|
-
<div class="
|
|
1137
|
-
<div
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
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
|
-
|
|
1161
|
-
return `
|
|
1147
|
+
<div class="controls">
|
|
1148
|
+
<div>
|
|
1149
|
+
<button class="btn btn-secondary" onclick="resetAll()">🔄 Reset All</button>
|
|
1150
|
+
</div>
|
|
1151
|
+
<div>
|
|
1152
|
+
<button class="btn btn-primary" onclick="approveTestCases()">✅ Approve Test Cases</button>
|
|
1153
|
+
</div>
|
|
1154
|
+
</div>
|
|
1155
|
+
|
|
1156
|
+
<div class="test-cases">
|
|
1157
|
+
${processedTestCases.map((testCase, index) => `
|
|
1162
1158
|
<div class="test-case" data-index="${index}">
|
|
1163
1159
|
<div class="test-case-header">
|
|
1164
|
-
<
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
<div class="test-case-title">
|
|
1169
|
-
<textarea data-index="${index}" placeholder="Enter test case title and steps...">${safeTitle}${safeDescription ? '\n\nSteps:\n' + safeDescription : ''}</textarea>
|
|
1170
|
-
</div>
|
|
1171
|
-
${testCase.status === 'Modify' && testCase.originalCase ? `
|
|
1172
|
-
<div class="original-case">
|
|
1173
|
-
<strong>Original Test Case:</strong><br>
|
|
1174
|
-
${safeOriginal}
|
|
1175
|
-
</div>
|
|
1176
|
-
` : ''}
|
|
1177
|
-
${testCase.status === 'Remove' ? `
|
|
1178
|
-
<div class="remove-case">
|
|
1179
|
-
<strong>⚠️ Marked for Removal</strong><br>
|
|
1180
|
-
This test case is scheduled to be removed. You can restore it using the button below.
|
|
1181
|
-
</div>
|
|
1182
|
-
` : ''}
|
|
1183
|
-
<div class="test-case-actions">
|
|
1184
|
-
<button class="btn btn-delete" onclick="toggleDelete(${index})">Delete</button>
|
|
1160
|
+
<div class="test-case-meta">
|
|
1161
|
+
<span class="test-case-index">#${index + 1}</span>
|
|
1162
|
+
<span class="test-case-status status-${testCase.status.toLowerCase()}">${testCase.status}</span>
|
|
1163
|
+
${testCase.originalCase ? `<span style="font-size: 12px; color: #6c757d;">Ref: ${testCase.originalCase}</span>` : ''}
|
|
1185
1164
|
</div>
|
|
1165
|
+
<button class="btn btn-delete" onclick="toggleDelete(${index})">Delete</button>
|
|
1186
1166
|
</div>
|
|
1167
|
+
<textarea data-index="${index}" placeholder="Enter test case details...">${testCase.title}${testCase.description ? '\n\nSteps:\n' + testCase.description : ''}</textarea>
|
|
1187
1168
|
</div>
|
|
1188
|
-
|
|
1189
|
-
}).join('')}
|
|
1190
|
-
</div>
|
|
1191
|
-
</div>
|
|
1192
|
-
|
|
1193
|
-
<div class="footer">
|
|
1194
|
-
<button class="btn-primary" onclick="approveTestCases()">Approve Test Cases</button>
|
|
1195
|
-
<button class="btn-secondary" onclick="resetAll()">Reset All</button>
|
|
1169
|
+
`).join('')}
|
|
1196
1170
|
</div>
|
|
1197
1171
|
</div>
|
|
1198
1172
|
|
|
1199
|
-
<div class="notification" id="notification"></div>
|
|
1200
|
-
|
|
1201
1173
|
<script>
|
|
1202
|
-
let testCases = ${JSON.stringify(processedTestCases)};
|
|
1174
|
+
let testCases = ${JSON.stringify(processedTestCases).replace(/</g, '\\u003c').replace(/>/g, '\\u003e')};
|
|
1203
1175
|
let deletedIndices = new Set();
|
|
1204
1176
|
|
|
1205
1177
|
function updateStats() {
|
|
@@ -1238,7 +1210,7 @@ tool(
|
|
|
1238
1210
|
// Reset textarea value
|
|
1239
1211
|
const textarea = el.querySelector('textarea');
|
|
1240
1212
|
const originalTestCase = testCases[index];
|
|
1241
|
-
const resetValue = originalTestCase.title + (originalTestCase.description ? '
|
|
1213
|
+
const resetValue = originalTestCase.title + (originalTestCase.description ? '\\n\\nSteps:\\n' + originalTestCase.description : '');
|
|
1242
1214
|
textarea.value = resetValue;
|
|
1243
1215
|
});
|
|
1244
1216
|
updateStats();
|
|
@@ -1285,7 +1257,7 @@ tool(
|
|
|
1285
1257
|
body: JSON.stringify({
|
|
1286
1258
|
sessionId: '${sessionId}',
|
|
1287
1259
|
testCases: updatedTestCases
|
|
1288
|
-
|
|
1260
|
+
})
|
|
1289
1261
|
})
|
|
1290
1262
|
.then(response => response.json())
|
|
1291
1263
|
.then(data => {
|
|
@@ -1313,8 +1285,8 @@ tool(
|
|
|
1313
1285
|
if (e.target.tagName === 'TEXTAREA') {
|
|
1314
1286
|
const index = parseInt(e.target.getAttribute('data-index'));
|
|
1315
1287
|
if (!isNaN(index) && testCases[index]) {
|
|
1316
|
-
testCases[index].title = e.target.value.split('
|
|
1317
|
-
const stepsIndex = e.target.value.indexOf('
|
|
1288
|
+
testCases[index].title = e.target.value.split('\\n\\nSteps:\\n')[0];
|
|
1289
|
+
const stepsIndex = e.target.value.indexOf('\\n\\nSteps:\\n');
|
|
1318
1290
|
if (stepsIndex !== -1) {
|
|
1319
1291
|
testCases[index].description = e.target.value.substring(stepsIndex + 9);
|
|
1320
1292
|
}
|
|
@@ -1324,7 +1296,8 @@ tool(
|
|
|
1324
1296
|
</script>
|
|
1325
1297
|
</body>
|
|
1326
1298
|
</html>
|
|
1327
|
-
|
|
1299
|
+
`;
|
|
1300
|
+
res.send(htmlContent);
|
|
1328
1301
|
} catch (error) {
|
|
1329
1302
|
console.error('Error rendering page:', error);
|
|
1330
1303
|
res.status(500).send('Error rendering page');
|
|
@@ -1355,7 +1328,9 @@ tool(
|
|
|
1355
1328
|
|
|
1356
1329
|
// Close server after approval
|
|
1357
1330
|
setTimeout(() => {
|
|
1358
|
-
server.
|
|
1331
|
+
if (server && server.listening) {
|
|
1332
|
+
server.close();
|
|
1333
|
+
}
|
|
1359
1334
|
}, 3000);
|
|
1360
1335
|
} catch (error) {
|
|
1361
1336
|
console.error('Approval error:', error);
|
|
@@ -1374,32 +1349,22 @@ tool(
|
|
|
1374
1349
|
res.status(404).json({ error: 'Not found' });
|
|
1375
1350
|
});
|
|
1376
1351
|
|
|
1377
|
-
// Start server with
|
|
1378
|
-
const server =
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1352
|
+
// Start server with promise-based approach
|
|
1353
|
+
const server = await new Promise((resolve, reject) => {
|
|
1354
|
+
const srv = app.listen(port, (err) => {
|
|
1355
|
+
if (err) {
|
|
1356
|
+
reject(err);
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
console.log(`✅ Test case review session started. Session ID: ${sessionId}.`);
|
|
1360
|
+
console.log(`Server running at http://localhost:${port}`);
|
|
1361
|
+
console.log(`Browser should open automatically.`);
|
|
1362
|
+
resolve(srv);
|
|
1363
|
+
});
|
|
1386
1364
|
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
// Try a different port
|
|
1391
|
-
const newPort = port + Math.floor(Math.random() * 100);
|
|
1392
|
-
const newServer = app.listen(newPort, () => {
|
|
1393
|
-
console.log(`Test case review server running at http://localhost:${newPort}`);
|
|
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
|
-
}
|
|
1399
|
-
});
|
|
1400
|
-
return newServer;
|
|
1401
|
-
}
|
|
1402
|
-
throw error;
|
|
1365
|
+
srv.on('error', (error) => {
|
|
1366
|
+
reject(error);
|
|
1367
|
+
});
|
|
1403
1368
|
});
|
|
1404
1369
|
|
|
1405
1370
|
// Open browser with proper error handling
|
|
@@ -1417,7 +1382,8 @@ tool(
|
|
|
1417
1382
|
global.approvalSessions[sessionId] = {
|
|
1418
1383
|
status: 'pending',
|
|
1419
1384
|
testCases: processedTestCases,
|
|
1420
|
-
timestamp: Date.now()
|
|
1385
|
+
timestamp: Date.now(),
|
|
1386
|
+
server: server
|
|
1421
1387
|
};
|
|
1422
1388
|
|
|
1423
1389
|
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.'}`;
|