@nbakka/mcp-appium 2.0.68 → 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 +241 -167
- 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,193 +667,264 @@ tool(
|
|
|
664
667
|
async ({ testCases }) => {
|
|
665
668
|
try {
|
|
666
669
|
const express = require('express');
|
|
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
|
+
});
|
|
667
679
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
<
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
</
|
|
748
|
-
<
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
`;
|
|
753
|
-
|
|
754
|
-
testCases.forEach(tc => {
|
|
755
|
-
html += `
|
|
756
|
-
<div class="test-case">
|
|
757
|
-
<div class="test-id">${tc.id}</div>
|
|
758
|
-
<div class="test-title">${tc.title}</div>
|
|
759
|
-
<div class="test-description">${tc.description}</div>
|
|
760
|
-
</div>
|
|
761
|
-
`;
|
|
762
|
-
});
|
|
763
|
-
|
|
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
|
+
`;
|
|
762
|
+
|
|
763
|
+
testCases.forEach(tc => {
|
|
764
764
|
html += `
|
|
765
|
-
|
|
766
|
-
<div class="
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
</div>
|
|
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>
|
|
770
769
|
</div>
|
|
771
|
-
|
|
772
|
-
<script>
|
|
773
|
-
function approveTestCases() {
|
|
774
|
-
document.querySelector('button').disabled = true;
|
|
775
|
-
document.querySelector('button').style.background = '#6c757d';
|
|
776
|
-
document.querySelector('button').innerHTML = 'Processing...';
|
|
777
|
-
|
|
778
|
-
const status = document.getElementById('status');
|
|
779
|
-
status.style.display = 'block';
|
|
780
|
-
status.style.color = '#28a745';
|
|
781
|
-
status.innerHTML = 'Test cases approved! You can close this window.';
|
|
782
|
-
|
|
783
|
-
fetch('/approve')
|
|
784
|
-
.then(response => response.text())
|
|
785
|
-
.then(data => {
|
|
786
|
-
status.innerHTML = data + ' You can close this window.';
|
|
787
|
-
setTimeout(() => {
|
|
788
|
-
window.close();
|
|
789
|
-
}, 2000);
|
|
790
|
-
})
|
|
791
|
-
.catch(error => {
|
|
792
|
-
status.style.color = '#dc3545';
|
|
793
|
-
status.innerHTML = 'Error: ' + error.message;
|
|
794
|
-
});
|
|
795
|
-
}
|
|
796
|
-
</script>
|
|
797
|
-
</body>
|
|
798
|
-
</html>
|
|
799
770
|
`;
|
|
800
|
-
res.send(html);
|
|
801
771
|
});
|
|
802
772
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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
|
+
});
|
|
812
804
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
805
|
+
</script>
|
|
806
|
+
</body>
|
|
807
|
+
</html>
|
|
808
|
+
`;
|
|
809
|
+
res.send(html);
|
|
810
|
+
});
|
|
816
811
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
} catch (openError) {
|
|
827
|
-
console.log('Failed to open browser automatically:', openError.message);
|
|
828
|
-
console.log(`Please manually open: http://localhost:${port}`);
|
|
829
|
-
}
|
|
830
|
-
});
|
|
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
|
+
});
|
|
831
821
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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
|
+
});
|
|
836
836
|
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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();
|
|
842
849
|
}
|
|
843
|
-
|
|
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)"
|
|
844
861
|
});
|
|
845
862
|
|
|
846
863
|
} catch (err) {
|
|
847
|
-
return
|
|
864
|
+
return JSON.stringify({
|
|
865
|
+
status: "error",
|
|
866
|
+
message: `Error setting up review interface: ${err.message}`
|
|
867
|
+
});
|
|
848
868
|
}
|
|
849
869
|
}
|
|
850
870
|
);
|
|
851
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);
|
|
852
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);
|
|
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);
|
|
853
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
|
+
);
|
|
854
928
|
return server;
|
|
855
929
|
};
|
|
856
930
|
|