@nbakka/mcp-appium 2.0.67 → 2.0.69
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 +242 -62
- package/package.json +1 -1
package/lib/server.js
CHANGED
|
@@ -651,6 +651,9 @@ tool(
|
|
|
651
651
|
}
|
|
652
652
|
);
|
|
653
653
|
|
|
654
|
+
// Global state for tracking approval sessions
|
|
655
|
+
let approvalSessions = new Map();
|
|
656
|
+
|
|
654
657
|
tool(
|
|
655
658
|
"review_testcases",
|
|
656
659
|
"Open JSON test cases in browser for manual approval",
|
|
@@ -664,87 +667,264 @@ tool(
|
|
|
664
667
|
async ({ testCases }) => {
|
|
665
668
|
try {
|
|
666
669
|
const express = require('express');
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
let html = `
|
|
677
|
-
<html>
|
|
678
|
-
<head>
|
|
679
|
-
<title>Test Case Approval</title>
|
|
680
|
-
<style>
|
|
681
|
-
body { font-family: Arial, sans-serif; margin: 20px; }
|
|
682
|
-
h1 { color: #333; }
|
|
683
|
-
li { margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
|
|
684
|
-
button { padding: 10px 20px; background: #007cba; color: white; border: none; border-radius: 5px; cursor: pointer; }
|
|
685
|
-
button:hover { background: #005a87; }
|
|
686
|
-
</style>
|
|
687
|
-
</head>
|
|
688
|
-
<body>
|
|
689
|
-
<h1>Review Test Cases</h1>
|
|
690
|
-
<ul>
|
|
691
|
-
`;
|
|
670
|
+
const sessionId = Date.now().toString();
|
|
671
|
+
|
|
672
|
+
// Initialize session state
|
|
673
|
+
approvalSessions.set(sessionId, {
|
|
674
|
+
status: 'pending',
|
|
675
|
+
testCases: testCases,
|
|
676
|
+
server: null,
|
|
677
|
+
startTime: Date.now()
|
|
678
|
+
});
|
|
692
679
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
680
|
+
const app = express();
|
|
681
|
+
const port = 3001;
|
|
682
|
+
|
|
683
|
+
// Serve a simple HTML page with test cases
|
|
684
|
+
app.get("/", (req, res) => {
|
|
685
|
+
let html = `
|
|
686
|
+
<html>
|
|
687
|
+
<head>
|
|
688
|
+
<title>Test Case Approval</title>
|
|
689
|
+
<style>
|
|
690
|
+
body {
|
|
691
|
+
font-family: Arial, sans-serif;
|
|
692
|
+
margin: 20px;
|
|
693
|
+
background-color: #f5f5f5;
|
|
694
|
+
}
|
|
695
|
+
.container {
|
|
696
|
+
max-width: 1200px;
|
|
697
|
+
margin: 0 auto;
|
|
698
|
+
background: white;
|
|
699
|
+
padding: 20px;
|
|
700
|
+
border-radius: 8px;
|
|
701
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
702
|
+
}
|
|
703
|
+
h1 {
|
|
704
|
+
color: #333;
|
|
705
|
+
text-align: center;
|
|
706
|
+
border-bottom: 2px solid #007cba;
|
|
707
|
+
padding-bottom: 10px;
|
|
708
|
+
}
|
|
709
|
+
.test-case {
|
|
710
|
+
margin: 15px 0;
|
|
711
|
+
padding: 15px;
|
|
712
|
+
border: 1px solid #ddd;
|
|
713
|
+
border-radius: 8px;
|
|
714
|
+
background: #fafafa;
|
|
715
|
+
}
|
|
716
|
+
.test-id {
|
|
717
|
+
font-weight: bold;
|
|
718
|
+
color: #007cba;
|
|
719
|
+
font-size: 16px;
|
|
720
|
+
}
|
|
721
|
+
.test-title {
|
|
722
|
+
font-weight: bold;
|
|
723
|
+
margin: 5px 0;
|
|
724
|
+
color: #333;
|
|
725
|
+
}
|
|
726
|
+
.test-description {
|
|
727
|
+
color: #666;
|
|
728
|
+
line-height: 1.4;
|
|
729
|
+
}
|
|
730
|
+
.button-container {
|
|
731
|
+
text-align: center;
|
|
732
|
+
margin-top: 30px;
|
|
733
|
+
padding-top: 20px;
|
|
734
|
+
border-top: 1px solid #ddd;
|
|
735
|
+
}
|
|
736
|
+
button {
|
|
737
|
+
padding: 15px 30px;
|
|
738
|
+
background: #28a745;
|
|
739
|
+
color: white;
|
|
740
|
+
border: none;
|
|
741
|
+
border-radius: 5px;
|
|
742
|
+
cursor: pointer;
|
|
743
|
+
font-size: 16px;
|
|
744
|
+
font-weight: bold;
|
|
745
|
+
}
|
|
746
|
+
button:hover {
|
|
747
|
+
background: #218838;
|
|
748
|
+
}
|
|
749
|
+
.status {
|
|
750
|
+
text-align: center;
|
|
751
|
+
margin-top: 20px;
|
|
752
|
+
font-weight: bold;
|
|
753
|
+
display: none;
|
|
754
|
+
}
|
|
755
|
+
</style>
|
|
756
|
+
</head>
|
|
757
|
+
<body>
|
|
758
|
+
<div class="container">
|
|
759
|
+
<h1>Review Test Cases</h1>
|
|
760
|
+
<div id="test-cases">
|
|
761
|
+
`;
|
|
696
762
|
|
|
763
|
+
testCases.forEach(tc => {
|
|
697
764
|
html += `
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
765
|
+
<div class="test-case">
|
|
766
|
+
<div class="test-id">${tc.id}</div>
|
|
767
|
+
<div class="test-title">${tc.title}</div>
|
|
768
|
+
<div class="test-description">${tc.description}</div>
|
|
769
|
+
</div>
|
|
702
770
|
`;
|
|
703
|
-
res.send(html);
|
|
704
771
|
});
|
|
705
772
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
773
|
+
html += `
|
|
774
|
+
</div>
|
|
775
|
+
<div class="button-container">
|
|
776
|
+
<button onclick="approveTestCases()">✓ Approve Test Cases</button>
|
|
777
|
+
<div id="status" class="status"></div>
|
|
778
|
+
</div>
|
|
779
|
+
</div>
|
|
780
|
+
|
|
781
|
+
<script>
|
|
782
|
+
function approveTestCases() {
|
|
783
|
+
document.querySelector('button').disabled = true;
|
|
784
|
+
document.querySelector('button').style.background = '#6c757d';
|
|
785
|
+
document.querySelector('button').innerHTML = 'Processing...';
|
|
786
|
+
|
|
787
|
+
const status = document.getElementById('status');
|
|
788
|
+
status.style.display = 'block';
|
|
789
|
+
status.style.color = '#28a745';
|
|
790
|
+
status.innerHTML = 'Test cases approved! You can close this window.';
|
|
791
|
+
|
|
792
|
+
fetch('/approve/${sessionId}')
|
|
793
|
+
.then(response => response.text())
|
|
794
|
+
.then(data => {
|
|
795
|
+
status.innerHTML = data + ' You can close this window.';
|
|
796
|
+
setTimeout(() => {
|
|
797
|
+
window.close();
|
|
798
|
+
}, 2000);
|
|
799
|
+
})
|
|
800
|
+
.catch(error => {
|
|
801
|
+
status.style.color = '#dc3545';
|
|
802
|
+
status.innerHTML = 'Error: ' + error.message;
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
</script>
|
|
806
|
+
</body>
|
|
807
|
+
</html>
|
|
808
|
+
`;
|
|
809
|
+
res.send(html);
|
|
810
|
+
});
|
|
714
811
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
});
|
|
812
|
+
// Approval endpoint
|
|
813
|
+
app.get(`/approve/${sessionId}`, (req, res) => {
|
|
814
|
+
const session = approvalSessions.get(sessionId);
|
|
815
|
+
if (session) {
|
|
816
|
+
session.status = 'approved';
|
|
817
|
+
approvalSessions.set(sessionId, session);
|
|
818
|
+
}
|
|
819
|
+
res.send("✓ Test cases approved successfully!");
|
|
820
|
+
});
|
|
725
821
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
822
|
+
// Start server
|
|
823
|
+
const server = app.listen(port, async () => {
|
|
824
|
+
console.log(`Test case review server started on http://localhost:${port}`);
|
|
825
|
+
|
|
826
|
+
try {
|
|
827
|
+
// Use dynamic import for the open package (ES module)
|
|
828
|
+
const { default: open } = await import('open');
|
|
829
|
+
await open(`http://localhost:${port}`);
|
|
830
|
+
console.log('Browser opened successfully');
|
|
831
|
+
} catch (openError) {
|
|
832
|
+
console.log('Failed to open browser automatically:', openError.message);
|
|
833
|
+
console.log(`Please manually open: http://localhost:${port}`);
|
|
834
|
+
}
|
|
835
|
+
});
|
|
730
836
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
837
|
+
// Store server reference in session
|
|
838
|
+
const session = approvalSessions.get(sessionId);
|
|
839
|
+
session.server = server;
|
|
840
|
+
approvalSessions.set(sessionId, session);
|
|
841
|
+
|
|
842
|
+
// Auto cleanup after 5 minutes
|
|
843
|
+
setTimeout(() => {
|
|
844
|
+
const session = approvalSessions.get(sessionId);
|
|
845
|
+
if (session && session.status === 'pending') {
|
|
846
|
+
session.status = 'timeout';
|
|
847
|
+
if (session.server) {
|
|
848
|
+
session.server.close();
|
|
735
849
|
}
|
|
736
|
-
|
|
737
|
-
}
|
|
850
|
+
approvalSessions.set(sessionId, session);
|
|
851
|
+
}
|
|
852
|
+
}, 300000); // 5 minutes
|
|
853
|
+
|
|
854
|
+
return JSON.stringify({
|
|
855
|
+
status: "review_started",
|
|
856
|
+
sessionId: sessionId,
|
|
857
|
+
message: "Test case review interface opened in browser. Use check_approval_status tool to poll for approval.",
|
|
858
|
+
testCasesCount: testCases.length,
|
|
859
|
+
browserUrl: `http://localhost:${port}`,
|
|
860
|
+
instructions: "Poll every 10 seconds using check_approval_status tool until approved or timeout (5 minutes)"
|
|
738
861
|
});
|
|
739
862
|
|
|
740
863
|
} catch (err) {
|
|
741
|
-
return
|
|
864
|
+
return JSON.stringify({
|
|
865
|
+
status: "error",
|
|
866
|
+
message: `Error setting up review interface: ${err.message}`
|
|
867
|
+
});
|
|
742
868
|
}
|
|
743
869
|
}
|
|
744
870
|
);
|
|
745
871
|
|
|
872
|
+
tool(
|
|
873
|
+
"check_approval_status",
|
|
874
|
+
"Check the approval status of test cases review session",
|
|
875
|
+
{
|
|
876
|
+
sessionId: zod_1.z.string().describe("The session ID returned from review_testcases tool")
|
|
877
|
+
},
|
|
878
|
+
async ({ sessionId }) => {
|
|
879
|
+
const session = approvalSessions.get(sessionId);
|
|
880
|
+
|
|
881
|
+
if (!session) {
|
|
882
|
+
return JSON.stringify({
|
|
883
|
+
status: "error",
|
|
884
|
+
message: "Session not found. Invalid session ID."
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const currentTime = Date.now();
|
|
889
|
+
const elapsedTime = Math.floor((currentTime - session.startTime) / 1000);
|
|
890
|
+
|
|
891
|
+
if (session.status === 'approved') {
|
|
892
|
+
// Clean up session and close server
|
|
893
|
+
if (session.server) {
|
|
894
|
+
session.server.close();
|
|
895
|
+
}
|
|
896
|
+
approvalSessions.delete(sessionId);
|
|
746
897
|
|
|
898
|
+
return JSON.stringify({
|
|
899
|
+
status: "approved",
|
|
900
|
+
message: "Test cases have been approved by the user. Continuing with next operations...",
|
|
901
|
+
testCasesCount: session.testCases.length,
|
|
902
|
+
elapsedTime: elapsedTime
|
|
903
|
+
});
|
|
904
|
+
} else if (session.status === 'timeout' || elapsedTime > 300) { // 5 minutes
|
|
905
|
+
// Clean up session and close server
|
|
906
|
+
if (session.server) {
|
|
907
|
+
session.server.close();
|
|
908
|
+
}
|
|
909
|
+
approvalSessions.delete(sessionId);
|
|
747
910
|
|
|
911
|
+
return JSON.stringify({
|
|
912
|
+
status: "timeout",
|
|
913
|
+
message: "Review session timed out after 5 minutes. Test cases were not approved.",
|
|
914
|
+
testCasesCount: session.testCases.length,
|
|
915
|
+
elapsedTime: elapsedTime
|
|
916
|
+
});
|
|
917
|
+
} else {
|
|
918
|
+
return JSON.stringify({
|
|
919
|
+
status: "pending",
|
|
920
|
+
message: "Test cases are still pending approval. Continue polling.",
|
|
921
|
+
testCasesCount: session.testCases.length,
|
|
922
|
+
elapsedTime: elapsedTime,
|
|
923
|
+
remainingTime: Math.max(0, 300 - elapsedTime)
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
);
|
|
748
928
|
return server;
|
|
749
929
|
};
|
|
750
930
|
|