@nbakka/mcp-appium 2.0.87 → 2.0.89
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/review-ui/index.html +123 -26
- package/lib/server.js +56 -52
- package/package.json +1 -1
package/lib/review-ui/index.html
CHANGED
|
@@ -1,38 +1,135 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
2
|
+
<html lang="en">
|
|
3
3
|
<head>
|
|
4
|
-
<
|
|
5
|
-
<
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Test Case Review</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f5f5f5; }
|
|
9
|
+
.container { max-width: 1200px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; }
|
|
10
|
+
.section { margin-bottom: 30px; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
|
|
11
|
+
.new { background-color: #e7f5e7; border-color: #4caf50; }
|
|
12
|
+
.modify { background-color: #fff3cd; border-color: #ffc107; }
|
|
13
|
+
.remove { background-color: #f8d7da; border-color: #dc3545; }
|
|
14
|
+
.test-case { margin: 10px 0; padding: 10px; background: white; border-radius: 3px; }
|
|
15
|
+
button { padding: 10px 20px; margin: 10px; border: none; border-radius: 5px; cursor: pointer; }
|
|
16
|
+
.approve { background-color: #4caf50; color: white; }
|
|
17
|
+
.cancel { background-color: #dc3545; color: white; }
|
|
18
|
+
h2 { margin-top: 0; }
|
|
19
|
+
.original { color: #666; text-decoration: line-through; }
|
|
20
|
+
.modified { color: #000; font-weight: bold; }
|
|
21
|
+
</style>
|
|
6
22
|
</head>
|
|
7
23
|
<body>
|
|
8
24
|
<div class="container">
|
|
9
|
-
<h1>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
<h1>Test Case Review</h1>
|
|
26
|
+
<div id="loading">Loading test cases...</div>
|
|
27
|
+
<div id="content" style="display: none;">
|
|
28
|
+
<div id="new-section" class="section new">
|
|
29
|
+
<h2>New Test Cases</h2>
|
|
30
|
+
<div id="new-cases"></div>
|
|
31
|
+
</div>
|
|
32
|
+
<div id="modify-section" class="section modify">
|
|
33
|
+
<h2>Modified Test Cases</h2>
|
|
34
|
+
<div id="modify-cases"></div>
|
|
35
|
+
</div>
|
|
36
|
+
<div id="remove-section" class="section remove">
|
|
37
|
+
<h2>Test Cases to Remove</h2>
|
|
38
|
+
<div id="remove-cases"></div>
|
|
39
|
+
</div>
|
|
40
|
+
<div style="text-align: center; margin-top: 30px;">
|
|
41
|
+
<button class="approve" onclick="approve()">Approve All</button>
|
|
42
|
+
<button class="cancel" onclick="cancel()">Cancel</button>
|
|
43
|
+
</div>
|
|
15
44
|
</div>
|
|
45
|
+
</div>
|
|
16
46
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<div id="modified-testcases"></div>
|
|
21
|
-
</div>
|
|
47
|
+
<script>
|
|
48
|
+
let sessionId;
|
|
49
|
+
let testCases;
|
|
22
50
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
51
|
+
async function loadTestCases() {
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch('/api/testcases');
|
|
54
|
+
const data = await response.json();
|
|
55
|
+
sessionId = data.sessionId;
|
|
56
|
+
testCases = data.testCases;
|
|
28
57
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
58
|
+
document.getElementById('loading').style.display = 'none';
|
|
59
|
+
document.getElementById('content').style.display = 'block';
|
|
60
|
+
|
|
61
|
+
renderTestCases();
|
|
62
|
+
} catch (error) {
|
|
63
|
+
document.getElementById('loading').innerHTML = 'Error loading test cases: ' + error.message;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function renderTestCases() {
|
|
68
|
+
// Render new test cases
|
|
69
|
+
const newCases = document.getElementById('new-cases');
|
|
70
|
+
if (testCases.new && testCases.new.length > 0) {
|
|
71
|
+
newCases.innerHTML = testCases.new.map(tc =>
|
|
72
|
+
`<div class="test-case"><strong>ID:</strong> ${tc.id}<br><strong>Description:</strong> ${tc.description}</div>`
|
|
73
|
+
).join('');
|
|
74
|
+
} else {
|
|
75
|
+
newCases.innerHTML = '<p>No new test cases</p>';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Render modified test cases
|
|
79
|
+
const modifyCases = document.getElementById('modify-cases');
|
|
80
|
+
if (testCases.modify && testCases.modify.length > 0) {
|
|
81
|
+
modifyCases.innerHTML = testCases.modify.map(tc =>
|
|
82
|
+
`<div class="test-case">
|
|
83
|
+
<strong>ID:</strong> ${tc.id}<br>
|
|
84
|
+
<strong>Original:</strong> <span class="original">${tc.original}</span><br>
|
|
85
|
+
<strong>Modified:</strong> <span class="modified">${tc.modified}</span>
|
|
86
|
+
</div>`
|
|
87
|
+
).join('');
|
|
88
|
+
} else {
|
|
89
|
+
modifyCases.innerHTML = '<p>No modified test cases</p>';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Render remove test cases
|
|
93
|
+
const removeCases = document.getElementById('remove-cases');
|
|
94
|
+
if (testCases.remove && testCases.remove.length > 0) {
|
|
95
|
+
removeCases.innerHTML = testCases.remove.map(tc =>
|
|
96
|
+
`<div class="test-case"><strong>ID:</strong> ${tc.id}<br><strong>Description:</strong> ${tc.description}</div>`
|
|
97
|
+
).join('');
|
|
98
|
+
} else {
|
|
99
|
+
removeCases.innerHTML = '<p>No test cases to remove</p>';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function approve() {
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(`/approve/${sessionId}`, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: { 'Content-Type': 'application/json' },
|
|
108
|
+
body: JSON.stringify(testCases)
|
|
109
|
+
});
|
|
110
|
+
const result = await response.json();
|
|
111
|
+
alert(result.message);
|
|
112
|
+
window.close();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
alert('Error approving test cases: ' + error.message);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function cancel() {
|
|
119
|
+
try {
|
|
120
|
+
const response = await fetch(`/cancel/${sessionId}`, {
|
|
121
|
+
method: 'POST'
|
|
122
|
+
});
|
|
123
|
+
const result = await response.json();
|
|
124
|
+
alert(result.message);
|
|
125
|
+
window.close();
|
|
126
|
+
} catch (error) {
|
|
127
|
+
alert('Error cancelling review: ' + error.message);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
35
130
|
|
|
36
|
-
|
|
131
|
+
// Load test cases when page loads
|
|
132
|
+
window.onload = loadTestCases;
|
|
133
|
+
</script>
|
|
37
134
|
</body>
|
|
38
135
|
</html>
|
package/lib/server.js
CHANGED
|
@@ -754,7 +754,7 @@ ${guidelines}`
|
|
|
754
754
|
await fs.writeFile(testCasesFilePath, `Error generating test cases: ${error.message}`);
|
|
755
755
|
});
|
|
756
756
|
|
|
757
|
-
return "✅ Test case generation started. Use 'check_testcases_status' tool to check if generation is complete.";
|
|
757
|
+
return "✅ Test case generation started. Use 'check_testcases_status' tool to check if generation is complete. Try max 10 times";
|
|
758
758
|
} catch (err) {
|
|
759
759
|
return `❌ Error starting test case generation: ${err.message}`;
|
|
760
760
|
}
|
|
@@ -768,7 +768,7 @@ tool(
|
|
|
768
768
|
async () => {
|
|
769
769
|
try {
|
|
770
770
|
// Wait for 20 seconds before checking
|
|
771
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
771
|
+
await new Promise(resolve => setTimeout(resolve, 25000));
|
|
772
772
|
|
|
773
773
|
const testCasesFilePath = path.join(__dirname, 'generated-testcases.txt');
|
|
774
774
|
|
|
@@ -816,36 +816,46 @@ tool(
|
|
|
816
816
|
remove: []
|
|
817
817
|
};
|
|
818
818
|
|
|
819
|
-
testCases.forEach(tc => {
|
|
820
|
-
if (!tc || tc.length < 2)
|
|
819
|
+
testCases.forEach((tc, index) => {
|
|
820
|
+
if (!tc || !Array.isArray(tc) || tc.length < 2) {
|
|
821
|
+
console.log(`Skipping invalid test case at index ${index}:`, tc);
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
const lastElement = tc[tc.length - 1];
|
|
826
|
+
const secondLastElement = tc.length > 1 ? tc[tc.length - 2] : null;
|
|
821
827
|
|
|
822
|
-
if (
|
|
828
|
+
if (lastElement?.toLowerCase() === 'new') {
|
|
823
829
|
// Format: [description, "New"]
|
|
824
|
-
parsedTestCases.new.push(
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
}
|
|
830
|
+
parsedTestCases.new.push({
|
|
831
|
+
description: tc[0],
|
|
832
|
+
id: `NEW-${index + 1}`
|
|
833
|
+
});
|
|
834
|
+
} else if (secondLastElement?.toLowerCase() === 'modify' && tc.length === 4) {
|
|
835
|
+
// Format: [originalDescription, newDescription, "Modify", testCaseId]
|
|
836
|
+
parsedTestCases.modify.push({
|
|
837
|
+
id: tc[3],
|
|
838
|
+
original: tc[0],
|
|
839
|
+
modified: tc[1]
|
|
840
|
+
});
|
|
841
|
+
} else if (secondLastElement?.toLowerCase() === 'remove' && tc.length === 3) {
|
|
842
|
+
// Format: [description, "Remove", testCaseId]
|
|
843
|
+
parsedTestCases.remove.push({
|
|
844
|
+
id: tc[2],
|
|
845
|
+
description: tc[0]
|
|
846
|
+
});
|
|
847
|
+
} else {
|
|
848
|
+
console.log(`Unrecognized test case format at index ${index}:`, tc);
|
|
841
849
|
}
|
|
842
850
|
});
|
|
843
851
|
|
|
852
|
+
console.log('Parsed test cases:', JSON.stringify(parsedTestCases, null, 2));
|
|
853
|
+
|
|
844
854
|
// Initialize session state
|
|
845
855
|
approvalSessions.set(sessionId, {
|
|
846
856
|
status: 'pending',
|
|
847
857
|
testCases: parsedTestCases,
|
|
848
|
-
originalTestCases: JSON.parse(JSON.stringify(parsedTestCases)),
|
|
858
|
+
originalTestCases: JSON.parse(JSON.stringify(parsedTestCases)),
|
|
849
859
|
server: null,
|
|
850
860
|
startTime: Date.now()
|
|
851
861
|
});
|
|
@@ -853,25 +863,24 @@ tool(
|
|
|
853
863
|
const app = express();
|
|
854
864
|
const port = 3001;
|
|
855
865
|
|
|
856
|
-
// Middleware for JSON parsing
|
|
857
866
|
app.use(express.json());
|
|
858
867
|
app.use(express.static(path.join(__dirname, 'review-ui')));
|
|
859
868
|
|
|
860
|
-
// Main page
|
|
861
869
|
app.get("/", (req, res) => {
|
|
862
870
|
res.sendFile(path.join(__dirname, 'review-ui', 'index.html'));
|
|
863
871
|
});
|
|
864
872
|
|
|
865
|
-
// API to get test cases data
|
|
866
873
|
app.get("/api/testcases", (req, res) => {
|
|
867
874
|
const session = approvalSessions.get(sessionId);
|
|
875
|
+
if (!session) {
|
|
876
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
877
|
+
}
|
|
868
878
|
res.json({
|
|
869
879
|
sessionId: sessionId,
|
|
870
|
-
testCases: session
|
|
880
|
+
testCases: session.testCases
|
|
871
881
|
});
|
|
872
882
|
});
|
|
873
883
|
|
|
874
|
-
// Approval endpoint
|
|
875
884
|
app.post(`/approve/${sessionId}`, (req, res) => {
|
|
876
885
|
const session = approvalSessions.get(sessionId);
|
|
877
886
|
if (session) {
|
|
@@ -879,38 +888,32 @@ tool(
|
|
|
879
888
|
session.finalTestCases = req.body;
|
|
880
889
|
approvalSessions.set(sessionId, session);
|
|
881
890
|
}
|
|
882
|
-
res.
|
|
891
|
+
res.json({ status: 'approved', message: 'Test cases approved successfully!' });
|
|
883
892
|
});
|
|
884
893
|
|
|
885
|
-
// Cancel endpoint
|
|
886
894
|
app.post(`/cancel/${sessionId}`, (req, res) => {
|
|
887
895
|
const session = approvalSessions.get(sessionId);
|
|
888
896
|
if (session) {
|
|
889
897
|
session.status = 'cancelled';
|
|
890
898
|
approvalSessions.set(sessionId, session);
|
|
891
899
|
}
|
|
892
|
-
res.
|
|
900
|
+
res.json({ status: 'cancelled', message: 'Review cancelled' });
|
|
893
901
|
});
|
|
894
902
|
|
|
895
|
-
// Start server
|
|
896
903
|
const server = app.listen(port, async () => {
|
|
897
904
|
console.log(`Test case review server started on http://localhost:${port}`);
|
|
898
|
-
|
|
899
905
|
try {
|
|
900
906
|
const { default: open } = await import('open');
|
|
901
907
|
await open(`http://localhost:${port}`);
|
|
902
|
-
console.log('Browser opened successfully');
|
|
903
908
|
} catch (openError) {
|
|
904
909
|
console.log('Failed to open browser automatically:', openError.message);
|
|
905
910
|
}
|
|
906
911
|
});
|
|
907
912
|
|
|
908
|
-
// Store server reference in session
|
|
909
913
|
const session = approvalSessions.get(sessionId);
|
|
910
914
|
session.server = server;
|
|
911
915
|
approvalSessions.set(sessionId, session);
|
|
912
916
|
|
|
913
|
-
// Auto cleanup after 5 minutes
|
|
914
917
|
setTimeout(() => {
|
|
915
918
|
const session = approvalSessions.get(sessionId);
|
|
916
919
|
if (session && session.status === 'pending') {
|
|
@@ -920,22 +923,25 @@ tool(
|
|
|
920
923
|
}
|
|
921
924
|
approvalSessions.set(sessionId, session);
|
|
922
925
|
}
|
|
923
|
-
}, 300000);
|
|
926
|
+
}, 300000);
|
|
924
927
|
|
|
925
|
-
|
|
928
|
+
const response = {
|
|
926
929
|
status: "review_started",
|
|
927
930
|
sessionId: sessionId,
|
|
928
931
|
message: "Test case review interface opened in browser. Use check_approval_status tool to poll for approval.",
|
|
929
932
|
testCasesCount: testCases.length,
|
|
930
933
|
browserUrl: `http://localhost:${port}`,
|
|
931
934
|
instructions: "Poll every 25 seconds using check_approval_status tool until approved or timeout (5 minutes)"
|
|
932
|
-
}
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
return JSON.stringify(response);
|
|
933
938
|
|
|
934
939
|
} catch (err) {
|
|
935
|
-
|
|
940
|
+
const errorResponse = {
|
|
936
941
|
status: "error",
|
|
937
942
|
message: `Error setting up review interface: ${err.message}`
|
|
938
|
-
}
|
|
943
|
+
};
|
|
944
|
+
return JSON.stringify(errorResponse);
|
|
939
945
|
}
|
|
940
946
|
}
|
|
941
947
|
);
|
|
@@ -948,11 +954,12 @@ tool(
|
|
|
948
954
|
},
|
|
949
955
|
async ({ sessionId }) => {
|
|
950
956
|
await new Promise(resolve => setTimeout(resolve, 25000));
|
|
957
|
+
|
|
951
958
|
const session = approvalSessions.get(sessionId);
|
|
952
959
|
if (!session) {
|
|
953
960
|
return JSON.stringify({
|
|
954
961
|
status: "error",
|
|
955
|
-
message:
|
|
962
|
+
message: `Session not found. Invalid session ID: ${sessionId}`
|
|
956
963
|
});
|
|
957
964
|
}
|
|
958
965
|
|
|
@@ -962,7 +969,6 @@ tool(
|
|
|
962
969
|
if (session.status === 'approved') {
|
|
963
970
|
const approvedTestCases = session.finalTestCases || session.testCases;
|
|
964
971
|
|
|
965
|
-
// Clean up session and close server
|
|
966
972
|
if (session.server) {
|
|
967
973
|
session.server.close();
|
|
968
974
|
}
|
|
@@ -970,12 +976,11 @@ tool(
|
|
|
970
976
|
|
|
971
977
|
return JSON.stringify({
|
|
972
978
|
status: "approved",
|
|
973
|
-
message: "Test cases
|
|
974
|
-
|
|
975
|
-
|
|
979
|
+
message: "Test cases approved successfully!",
|
|
980
|
+
elapsedTime: elapsedTime,
|
|
981
|
+
approvedTestCases: approvedTestCases
|
|
976
982
|
});
|
|
977
983
|
} else if (session.status === 'cancelled') {
|
|
978
|
-
// Clean up session and close server
|
|
979
984
|
if (session.server) {
|
|
980
985
|
session.server.close();
|
|
981
986
|
}
|
|
@@ -983,11 +988,10 @@ tool(
|
|
|
983
988
|
|
|
984
989
|
return JSON.stringify({
|
|
985
990
|
status: "cancelled",
|
|
986
|
-
message: "Review was cancelled by the user
|
|
991
|
+
message: "Review was cancelled by the user",
|
|
987
992
|
elapsedTime: elapsedTime
|
|
988
993
|
});
|
|
989
|
-
} else if (session.status === 'timeout' || elapsedTime > 300) {
|
|
990
|
-
// Clean up session and close server
|
|
994
|
+
} else if (session.status === 'timeout' || elapsedTime > 300) {
|
|
991
995
|
if (session.server) {
|
|
992
996
|
session.server.close();
|
|
993
997
|
}
|
|
@@ -995,13 +999,13 @@ tool(
|
|
|
995
999
|
|
|
996
1000
|
return JSON.stringify({
|
|
997
1001
|
status: "timeout",
|
|
998
|
-
message:
|
|
1002
|
+
message: "Review session timed out",
|
|
999
1003
|
elapsedTime: elapsedTime
|
|
1000
1004
|
});
|
|
1001
1005
|
} else {
|
|
1002
1006
|
return JSON.stringify({
|
|
1003
1007
|
status: "pending",
|
|
1004
|
-
message: "Test cases are still pending approval
|
|
1008
|
+
message: "Test cases are still pending approval",
|
|
1005
1009
|
elapsedTime: elapsedTime,
|
|
1006
1010
|
remainingTime: Math.max(0, 300 - elapsedTime)
|
|
1007
1011
|
});
|