@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 +482 -214
- package/package.json +1 -1
- package/lib/review-ui/index.html +0 -774
- package/lib/review-ui/script.js +0 -176
- package/lib/review-ui/styles.css +0 -244
package/lib/server.js
CHANGED
|
@@ -794,276 +794,544 @@ tool(
|
|
|
794
794
|
}
|
|
795
795
|
);
|
|
796
796
|
|
|
797
|
-
//
|
|
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
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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
|
-
|
|
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
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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
|
-
|
|
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
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
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
|
-
|
|
874
|
-
|
|
875
|
+
.header p {
|
|
876
|
+
font-size: 1.1rem;
|
|
877
|
+
opacity: 0.9;
|
|
878
|
+
}
|
|
875
879
|
|
|
876
|
-
|
|
877
|
-
|
|
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
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
res.json({ sessionId, testCases: session.testCases });
|
|
884
|
-
});
|
|
888
|
+
.stat-item {
|
|
889
|
+
text-align: center;
|
|
890
|
+
}
|
|
885
891
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
892
|
+
.stat-number {
|
|
893
|
+
font-size: 2rem;
|
|
894
|
+
font-weight: bold;
|
|
895
|
+
color: #495057;
|
|
896
|
+
}
|
|
889
897
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
898
|
+
.stat-label {
|
|
899
|
+
color: #6c757d;
|
|
900
|
+
margin-top: 5px;
|
|
893
901
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
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
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
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
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
922
|
+
.test-case.deleted {
|
|
923
|
+
opacity: 0.5;
|
|
924
|
+
background: #f8f9fa;
|
|
925
|
+
border-color: #dc3545;
|
|
926
|
+
}
|
|
923
927
|
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
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
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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
|
-
|
|
940
|
-
|
|
941
|
-
|
|
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
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
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
|
-
//
|
|
972
|
-
const
|
|
973
|
-
|
|
974
|
-
|
|
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
|
-
|
|
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
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1009
|
-
{
|
|
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
|
-
|
|
1012
|
-
|
|
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
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
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
|
-
|
|
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
|
|