@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.
@@ -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,42 +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 >= 3) {
826
- const type = tc[2]?.toLowerCase();
827
- if (type === 'modify' && tc.length >= 4) {
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] or [description, "", "Remove", testCaseId]
836
- parsedTestCases.remove.push({
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] || 'N/A',
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)), // Deep copy
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 ? session.testCases : {}
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.send("✓ Test cases approved successfully!");
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.send("Review cancelled");
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); // 5 minutes
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 `✅ Test case review interface opened in browser.
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
- return `❌ Error setting up review interface: ${err.message}`;
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 `❌ Session not found. Invalid session ID: ${sessionId}`;
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 `✅ Test cases approved successfully!
969
- Elapsed time: ${elapsedTime} seconds
970
- Approved test cases: ${JSON.stringify(approvedTestCases, null, 2)}`;
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 `❌ Review was cancelled by the user. Elapsed time: ${elapsedTime} seconds`;
979
- } else if (session.status === 'timeout' || elapsedTime > 300) { // 5 minutes
980
- // Clean up session and close server
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 `⏰ Review session timed out. Elapsed time: ${elapsedTime} seconds`;
1000
+ return JSON.stringify({
1001
+ status: "timeout",
1002
+ message: "Review session timed out",
1003
+ elapsedTime: elapsedTime
1004
+ });
987
1005
  } else {
988
- return `⏳ Test cases are still pending approval.
989
- Elapsed time: ${elapsedTime} seconds
990
- Remaining time: ${Math.max(0, 300 - elapsedTime)} seconds
991
- Continue polling with check_approval_status tool.`;
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
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbakka/mcp-appium",
3
- "version": "2.0.88",
3
+ "version": "2.0.89",
4
4
  "description": "Appium MCP",
5
5
  "engines": {
6
6
  "node": ">=18"