@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.
@@ -1,38 +1,135 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html lang="en">
3
3
  <head>
4
- <title>Test Case Review & Approval</title>
5
- <link rel="stylesheet" href="/styles.css">
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>Review Test Cases</h1>
10
-
11
- <!-- New Test Cases Section -->
12
- <div class="section" id="new-testcases-section">
13
- <h2>New Test Cases</h2>
14
- <div id="new-testcases"></div>
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
- <!-- Modified Test Cases Section -->
18
- <div class="section" id="modified-testcases-section">
19
- <h2>Modified Test Cases</h2>
20
- <div id="modified-testcases"></div>
21
- </div>
47
+ <script>
48
+ let sessionId;
49
+ let testCases;
22
50
 
23
- <!-- Test Cases to Remove Section -->
24
- <div class="section" id="remove-testcases-section">
25
- <h2>Test Cases to Remove</h2>
26
- <div id="remove-testcases"></div>
27
- </div>
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
- <div class="button-container">
30
- <button id="approve-btn" onclick="approveTestCases()">✓ Approve Test Cases</button>
31
- <button id="cancel-btn" onclick="cancelReview()">✗ Cancel Review</button>
32
- <div id="status" class="status"></div>
33
- </div>
34
- </div>
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
- <script src="/script.js"></script>
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, 20000));
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) return; // Skip invalid entries
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 (tc.length === 2 && tc[1].toLowerCase() === 'new') {
828
+ if (lastElement?.toLowerCase() === 'new') {
823
829
  // Format: [description, "New"]
824
- parsedTestCases.new.push(tc[0]);
825
- } else if (tc.length === 4) {
826
- const type = tc[2]?.toLowerCase();
827
- if (type === 'modify') {
828
- // Format: [originalDescription, newDescription, "Modify", testCaseId]
829
- parsedTestCases.modify.push({
830
- id: tc[3] || 'N/A',
831
- original: tc[0], // Original description
832
- modified: tc[1] // New/modified description
833
- });
834
- } else if (type === 'remove') {
835
- // Format: [description, "", "Remove", testCaseId]
836
- parsedTestCases.remove.push({
837
- id: tc[3] || 'N/A',
838
- description: tc[0]
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)), // Deep copy
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 ? session.testCases : {}
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.send("✓ Test cases approved successfully!");
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.send("Review cancelled");
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); // 5 minutes
926
+ }, 300000);
924
927
 
925
- return JSON.stringify({
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
- return JSON.stringify({
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: "Session not found. Invalid session ID."
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 have been approved by the user.",
974
- approvedTestCases: approvedTestCases,
975
- elapsedTime: elapsedTime
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) { // 5 minutes
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: `Review session timed out. Elapsed time: ${elapsedTime} seconds`,
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. Continue polling.",
1008
+ message: "Test cases are still pending approval",
1005
1009
  elapsedTime: elapsedTime,
1006
1010
  remainingTime: Math.max(0, 300 - elapsedTime)
1007
1011
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbakka/mcp-appium",
3
- "version": "2.0.87",
3
+ "version": "2.0.89",
4
4
  "description": "Appium MCP",
5
5
  "engines": {
6
6
  "node": ">=18"