@nbakka/mcp-appium 2.0.88 → 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 +80 -60
- 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,42 +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
|
-
id: tc.length >= 4 ? tc[3] : tc[1], // testCaseId could be in position 1 or 3
|
|
838
|
-
description: tc[0]
|
|
839
|
-
});
|
|
840
|
-
}
|
|
841
|
-
} else if (tc.length === 3 && tc[1].toLowerCase() === 'remove') {
|
|
842
|
-
// Handle format: [description, "Remove", testCaseId]
|
|
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
843
|
parsedTestCases.remove.push({
|
|
844
|
-
id: tc[2]
|
|
844
|
+
id: tc[2],
|
|
845
845
|
description: tc[0]
|
|
846
846
|
});
|
|
847
|
+
} else {
|
|
848
|
+
console.log(`Unrecognized test case format at index ${index}:`, tc);
|
|
847
849
|
}
|
|
848
850
|
});
|
|
849
851
|
|
|
852
|
+
console.log('Parsed test cases:', JSON.stringify(parsedTestCases, null, 2));
|
|
853
|
+
|
|
850
854
|
// Initialize session state
|
|
851
855
|
approvalSessions.set(sessionId, {
|
|
852
856
|
status: 'pending',
|
|
853
857
|
testCases: parsedTestCases,
|
|
854
|
-
originalTestCases: JSON.parse(JSON.stringify(parsedTestCases)),
|
|
858
|
+
originalTestCases: JSON.parse(JSON.stringify(parsedTestCases)),
|
|
855
859
|
server: null,
|
|
856
860
|
startTime: Date.now()
|
|
857
861
|
});
|
|
@@ -859,25 +863,24 @@ tool(
|
|
|
859
863
|
const app = express();
|
|
860
864
|
const port = 3001;
|
|
861
865
|
|
|
862
|
-
// Middleware for JSON parsing
|
|
863
866
|
app.use(express.json());
|
|
864
867
|
app.use(express.static(path.join(__dirname, 'review-ui')));
|
|
865
868
|
|
|
866
|
-
// Main page
|
|
867
869
|
app.get("/", (req, res) => {
|
|
868
870
|
res.sendFile(path.join(__dirname, 'review-ui', 'index.html'));
|
|
869
871
|
});
|
|
870
872
|
|
|
871
|
-
// API to get test cases data
|
|
872
873
|
app.get("/api/testcases", (req, res) => {
|
|
873
874
|
const session = approvalSessions.get(sessionId);
|
|
875
|
+
if (!session) {
|
|
876
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
877
|
+
}
|
|
874
878
|
res.json({
|
|
875
879
|
sessionId: sessionId,
|
|
876
|
-
testCases: session
|
|
880
|
+
testCases: session.testCases
|
|
877
881
|
});
|
|
878
882
|
});
|
|
879
883
|
|
|
880
|
-
// Approval endpoint
|
|
881
884
|
app.post(`/approve/${sessionId}`, (req, res) => {
|
|
882
885
|
const session = approvalSessions.get(sessionId);
|
|
883
886
|
if (session) {
|
|
@@ -885,38 +888,32 @@ tool(
|
|
|
885
888
|
session.finalTestCases = req.body;
|
|
886
889
|
approvalSessions.set(sessionId, session);
|
|
887
890
|
}
|
|
888
|
-
res.
|
|
891
|
+
res.json({ status: 'approved', message: 'Test cases approved successfully!' });
|
|
889
892
|
});
|
|
890
893
|
|
|
891
|
-
// Cancel endpoint
|
|
892
894
|
app.post(`/cancel/${sessionId}`, (req, res) => {
|
|
893
895
|
const session = approvalSessions.get(sessionId);
|
|
894
896
|
if (session) {
|
|
895
897
|
session.status = 'cancelled';
|
|
896
898
|
approvalSessions.set(sessionId, session);
|
|
897
899
|
}
|
|
898
|
-
res.
|
|
900
|
+
res.json({ status: 'cancelled', message: 'Review cancelled' });
|
|
899
901
|
});
|
|
900
902
|
|
|
901
|
-
// Start server
|
|
902
903
|
const server = app.listen(port, async () => {
|
|
903
904
|
console.log(`Test case review server started on http://localhost:${port}`);
|
|
904
|
-
|
|
905
905
|
try {
|
|
906
906
|
const { default: open } = await import('open');
|
|
907
907
|
await open(`http://localhost:${port}`);
|
|
908
|
-
console.log('Browser opened successfully');
|
|
909
908
|
} catch (openError) {
|
|
910
909
|
console.log('Failed to open browser automatically:', openError.message);
|
|
911
910
|
}
|
|
912
911
|
});
|
|
913
912
|
|
|
914
|
-
// Store server reference in session
|
|
915
913
|
const session = approvalSessions.get(sessionId);
|
|
916
914
|
session.server = server;
|
|
917
915
|
approvalSessions.set(sessionId, session);
|
|
918
916
|
|
|
919
|
-
// Auto cleanup after 5 minutes
|
|
920
917
|
setTimeout(() => {
|
|
921
918
|
const session = approvalSessions.get(sessionId);
|
|
922
919
|
if (session && session.status === 'pending') {
|
|
@@ -926,16 +923,25 @@ tool(
|
|
|
926
923
|
}
|
|
927
924
|
approvalSessions.set(sessionId, session);
|
|
928
925
|
}
|
|
929
|
-
}, 300000);
|
|
926
|
+
}, 300000);
|
|
927
|
+
|
|
928
|
+
const response = {
|
|
929
|
+
status: "review_started",
|
|
930
|
+
sessionId: sessionId,
|
|
931
|
+
message: "Test case review interface opened in browser. Use check_approval_status tool to poll for approval.",
|
|
932
|
+
testCasesCount: testCases.length,
|
|
933
|
+
browserUrl: `http://localhost:${port}`,
|
|
934
|
+
instructions: "Poll every 25 seconds using check_approval_status tool until approved or timeout (5 minutes)"
|
|
935
|
+
};
|
|
930
936
|
|
|
931
|
-
return
|
|
932
|
-
Session ID: ${sessionId}
|
|
933
|
-
Test Cases Count: ${testCases.length}
|
|
934
|
-
Browser URL: http://localhost:${port}
|
|
935
|
-
Instructions: Use check_approval_status tool to poll for approval every 25 seconds.`;
|
|
937
|
+
return JSON.stringify(response);
|
|
936
938
|
|
|
937
939
|
} catch (err) {
|
|
938
|
-
|
|
940
|
+
const errorResponse = {
|
|
941
|
+
status: "error",
|
|
942
|
+
message: `Error setting up review interface: ${err.message}`
|
|
943
|
+
};
|
|
944
|
+
return JSON.stringify(errorResponse);
|
|
939
945
|
}
|
|
940
946
|
}
|
|
941
947
|
);
|
|
@@ -948,9 +954,13 @@ 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
|
-
return
|
|
960
|
+
return JSON.stringify({
|
|
961
|
+
status: "error",
|
|
962
|
+
message: `Session not found. Invalid session ID: ${sessionId}`
|
|
963
|
+
});
|
|
954
964
|
}
|
|
955
965
|
|
|
956
966
|
const currentTime = Date.now();
|
|
@@ -959,36 +969,46 @@ tool(
|
|
|
959
969
|
if (session.status === 'approved') {
|
|
960
970
|
const approvedTestCases = session.finalTestCases || session.testCases;
|
|
961
971
|
|
|
962
|
-
// Clean up session and close server
|
|
963
972
|
if (session.server) {
|
|
964
973
|
session.server.close();
|
|
965
974
|
}
|
|
966
975
|
approvalSessions.delete(sessionId);
|
|
967
976
|
|
|
968
|
-
return
|
|
969
|
-
|
|
970
|
-
|
|
977
|
+
return JSON.stringify({
|
|
978
|
+
status: "approved",
|
|
979
|
+
message: "Test cases approved successfully!",
|
|
980
|
+
elapsedTime: elapsedTime,
|
|
981
|
+
approvedTestCases: approvedTestCases
|
|
982
|
+
});
|
|
971
983
|
} else if (session.status === 'cancelled') {
|
|
972
|
-
// Clean up session and close server
|
|
973
984
|
if (session.server) {
|
|
974
985
|
session.server.close();
|
|
975
986
|
}
|
|
976
987
|
approvalSessions.delete(sessionId);
|
|
977
988
|
|
|
978
|
-
return
|
|
979
|
-
|
|
980
|
-
|
|
989
|
+
return JSON.stringify({
|
|
990
|
+
status: "cancelled",
|
|
991
|
+
message: "Review was cancelled by the user",
|
|
992
|
+
elapsedTime: elapsedTime
|
|
993
|
+
});
|
|
994
|
+
} else if (session.status === 'timeout' || elapsedTime > 300) {
|
|
981
995
|
if (session.server) {
|
|
982
996
|
session.server.close();
|
|
983
997
|
}
|
|
984
998
|
approvalSessions.delete(sessionId);
|
|
985
999
|
|
|
986
|
-
return
|
|
1000
|
+
return JSON.stringify({
|
|
1001
|
+
status: "timeout",
|
|
1002
|
+
message: "Review session timed out",
|
|
1003
|
+
elapsedTime: elapsedTime
|
|
1004
|
+
});
|
|
987
1005
|
} else {
|
|
988
|
-
return
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1006
|
+
return JSON.stringify({
|
|
1007
|
+
status: "pending",
|
|
1008
|
+
message: "Test cases are still pending approval",
|
|
1009
|
+
elapsedTime: elapsedTime,
|
|
1010
|
+
remainingTime: Math.max(0, 300 - elapsedTime)
|
|
1011
|
+
});
|
|
992
1012
|
}
|
|
993
1013
|
}
|
|
994
1014
|
);
|