@nbakka/mcp-appium 2.0.52 → 2.0.54

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.
Files changed (2) hide show
  1. package/lib/server.js +300 -30
  2. package/package.json +1 -1
package/lib/server.js CHANGED
@@ -476,20 +476,33 @@ const createMcpServer = () => {
476
476
  }
477
477
 
478
478
  // ----------------------
479
- // Helper: Extract File ID
479
+ // Helper: Extract File ID & Node ID
480
480
  // ----------------------
481
- function extractFileIdFromUrl(url) {
481
+ function extractFileAndNodeId(url) {
482
482
  const patterns = [
483
483
  /figma\.com\/file\/([a-zA-Z0-9]+)/,
484
484
  /figma\.com\/design\/([a-zA-Z0-9]+)/,
485
485
  /figma\.com\/proto\/([a-zA-Z0-9]+)/
486
486
  ];
487
487
 
488
+ let fileId = null;
488
489
  for (const pattern of patterns) {
489
490
  const match = url.match(pattern);
490
- if (match) return match[1];
491
+ if (match) {
492
+ fileId = match[1];
493
+ break;
494
+ }
495
+ }
496
+
497
+ // Extract node-id if present
498
+ const nodeMatch = url.match(/[?&]node-id=([^&]+)/);
499
+ let nodeId = null;
500
+ if (nodeMatch) {
501
+ // Replace dash with colon (Figma expects 13:5951 instead of 13-5951)
502
+ nodeId = decodeURIComponent(nodeMatch[1]).replace(/-/g, ":");
491
503
  }
492
- return null;
504
+
505
+ return { fileId, nodeId };
493
506
  }
494
507
 
495
508
  // ----------------------
@@ -510,31 +523,38 @@ tool(
510
523
 
511
524
  if (!figmaToken) throw new Error("Figma API token missing in figma.json");
512
525
 
513
- // Extract file ID from URL
514
- const fileId = extractFileIdFromUrl(figmaUrl);
526
+ // Extract fileId and nodeId from URL
527
+ const { fileId, nodeId } = extractFileAndNodeId(figmaUrl);
515
528
  if (!fileId) throw new Error("Invalid Figma URL - cannot extract fileId");
516
529
 
517
- // Get file structure to find frames
518
- const fileResponse = await axios.get(
519
- `https://api.figma.com/v1/files/${fileId}`,
520
- { headers: { "X-Figma-Token": figmaToken } }
521
- );
530
+ let idsToExport = [];
531
+
532
+ if (nodeId) {
533
+ // Use node-id directly from URL
534
+ idsToExport = [nodeId];
535
+ } else {
536
+ // Fallback: scan file to collect all top-level frames
537
+ const fileResponse = await axios.get(
538
+ `https://api.figma.com/v1/files/${fileId}`,
539
+ { headers: { "X-Figma-Token": figmaToken } }
540
+ );
522
541
 
523
- const frameIds = [];
524
- fileResponse.data.document.children?.forEach(page => {
525
- page.children?.forEach(child => {
526
- if (child.type === "FRAME") frameIds.push(child.id);
542
+ fileResponse.data.document.children?.forEach(page => {
543
+ page.children?.forEach(child => {
544
+ if (child.type === "FRAME") idsToExport.push(child.id);
545
+ });
527
546
  });
528
- });
529
547
 
530
- if (frameIds.length === 0) throw new Error("No frames found in Figma file");
548
+ if (idsToExport.length === 0)
549
+ throw new Error("No frames found in Figma file");
550
+ }
531
551
 
532
552
  // Request PDF export
533
553
  const exportResponse = await axios.get(
534
554
  `https://api.figma.com/v1/images/${fileId}`,
535
555
  {
536
556
  headers: { "X-Figma-Token": figmaToken },
537
- params: { ids: frameIds.join(","), format: "pdf" }
557
+ params: { ids: idsToExport.join(","), format: "pdf" }
538
558
  }
539
559
  );
540
560
 
@@ -558,12 +578,13 @@ tool(
558
578
  }
559
579
  );
560
580
 
581
+
561
582
  // ----------------------
562
- // TOOL 2: Upload PDF to OpenAI with Jira Info
583
+ // TOOL: Generate Manual Test Cases from PDF + JIRA
563
584
  // ----------------------
564
585
  tool(
565
586
  "upload_pdf_to_openai",
566
- "Upload a PDF to OpenAI API along with Jira summary & description",
587
+ "Generate manual test cases by analyzing PDF design with JIRA requirements",
567
588
  {
568
589
  jiraSummary: zod_1.z.string().describe("Jira issue summary"),
569
590
  jiraDescription: zod_1.z.string().describe("Jira issue description"),
@@ -580,28 +601,277 @@ tool(
580
601
 
581
602
  const client = new OpenAI({ apiKey: openaiKey });
582
603
 
583
- // Read PDF file
584
- const pdfData = await fs.readFile(pdfPath);
604
+ // Validate file exists
605
+ if (!await fs.access(pdfPath).then(() => true).catch(() => false)) {
606
+ throw new Error(`PDF file not found at path: ${pdfPath}`);
607
+ }
585
608
 
586
- // Upload file to OpenAI
609
+ const stats = await fs.stat(pdfPath);
610
+ const fileName = path.basename(pdfPath);
611
+
612
+ console.log(`📄 Processing ${fileName} for test case generation...`);
613
+
614
+ // Upload PDF to OpenAI
587
615
  const fileUpload = await client.files.create({
588
- file: new Blob([pdfData], { type: "application/pdf" }),
616
+ file: fs.createReadStream(pdfPath),
589
617
  purpose: "assistants"
590
618
  });
591
619
 
592
- // Create a random prompt with Jira details
593
- const prompt = `Analyze this design based on Jira details:\n\nSummary: ${jiraSummary}\nDescription: ${jiraDescription}\n\nProvide suggestions and insights.`;
620
+ console.log(`✅ PDF uploaded: ${fileUpload.id}`);
621
+
622
+ // Create specialized assistant for test case generation
623
+ const assistant = await client.beta.assistants.create({
624
+ name: "Manual Test Case Generator",
625
+ instructions: `You are an expert QA engineer specializing in creating comprehensive manual test cases.
626
+ You analyze design documents and requirements to generate detailed, actionable test cases that cover:
627
+ - Functional testing scenarios
628
+ - UI/UX validation tests
629
+ - Edge cases and negative scenarios
630
+ - Cross-browser/device compatibility tests
631
+ - Accessibility testing scenarios`,
632
+ model: "gpt-4o", // Best model for complex reasoning
633
+ tools: [{ type: "file_search" }],
634
+ tool_resources: {
635
+ file_search: {
636
+ vector_stores: [{
637
+ file_ids: [fileUpload.id]
638
+ }]
639
+ }
640
+ }
641
+ });
642
+
643
+ // Create comprehensive test case generation prompt
644
+ const testCasePrompt = `
645
+ **JIRA REQUIREMENTS:**
646
+ **Summary:** ${jiraSummary}
647
+ **Description:** ${jiraDescription}
648
+
649
+ **TASK:** Generate comprehensive manual test cases based on the attached PDF design and above JIRA requirements.
650
+
651
+ **OUTPUT FORMAT REQUIRED:**
652
+ Please structure your response as follows:
653
+
654
+ ## MANUAL TEST CASES
655
+
656
+ ### 1. FUNCTIONAL TEST CASES
657
+ **TC-F-001: [Test Case Title]**
658
+ - **Objective:** What this test validates
659
+ - **Preconditions:** Setup required before testing
660
+ - **Test Steps:**
661
+ 1. Step 1 action
662
+ 2. Step 2 action
663
+ 3. Step 3 action
664
+ - **Expected Result:** What should happen
665
+ - **Priority:** High/Medium/Low
666
+
667
+ ### 2. UI/UX TEST CASES
668
+ **TC-UI-001: [Test Case Title]**
669
+ [Same format as above]
670
+
671
+ ### 3. EDGE CASE & NEGATIVE TEST CASES
672
+ **TC-E-001: [Test Case Title]**
673
+ [Same format as above]
674
+
675
+ ### 4. COMPATIBILITY TEST CASES
676
+ **TC-C-001: [Test Case Title]**
677
+ [Same format as above]
678
+
679
+ ### 5. ACCESSIBILITY TEST CASES
680
+ **TC-A-001: [Test Case Title]**
681
+ [Same format as above]
682
+
683
+ **IMPORTANT GUIDELINES:**
684
+ 1. Create 15-25 test cases total covering all aspects
685
+ 2. Reference specific UI elements from the PDF design
686
+ 3. Include both positive and negative test scenarios
687
+ 4. Consider different user roles if applicable
688
+ 5. Include performance and usability aspects
689
+ 6. Make test steps specific and actionable
690
+ 7. Consider mobile/responsive design if shown in PDF
691
+ 8. Include data validation test cases
692
+ 9. Cover error handling scenarios
693
+ 10. Include integration testing if multiple components interact
694
+
695
+ Generate detailed test cases now based on the PDF design and JIRA requirements.
696
+ `;
697
+
698
+ // Create thread and run analysis
699
+ const thread = await client.beta.threads.create({
700
+ messages: [
701
+ {
702
+ role: "user",
703
+ content: testCasePrompt
704
+ }
705
+ ]
706
+ });
707
+
708
+ console.log(`🔄 Generating test cases...`);
709
+
710
+ const run = await client.beta.threads.runs.create(thread.id, {
711
+ assistant_id: assistant.id,
712
+ });
713
+
714
+ // Wait for completion with progress tracking
715
+ let runStatus = await client.beta.threads.runs.retrieve(thread.id, run.id);
716
+ let attempts = 0;
717
+ const maxAttempts = 60; // 60 seconds for complex test case generation
718
+
719
+ while (runStatus.status !== 'completed' && attempts < maxAttempts) {
720
+ if (runStatus.status === 'failed') {
721
+ throw new Error(`Test case generation failed: ${runStatus.last_error?.message}`);
722
+ }
723
+
724
+ if (attempts % 5 === 0) {
725
+ console.log(`⏳ Still generating... (${attempts}s elapsed)`);
726
+ }
727
+
728
+ await new Promise(resolve => setTimeout(resolve, 1000));
729
+ runStatus = await client.beta.threads.runs.retrieve(thread.id, run.id);
730
+ attempts++;
731
+ }
732
+
733
+ if (runStatus.status !== 'completed') {
734
+ throw new Error('Test case generation timed out after 60 seconds');
735
+ }
736
+
737
+ // Get the generated test cases
738
+ const messages = await client.beta.threads.messages.list(thread.id);
739
+ const testCases = messages.data
740
+ .filter(msg => msg.role === 'assistant')
741
+ .map(msg => msg.content[0]?.text?.value)
742
+ .join('\n\n');
743
+
744
+ // Parse and structure the response
745
+ const testCaseCount = (testCases.match(/\*\*TC-/g) || []).length;
746
+
747
+ // Clean up assistant
748
+ try {
749
+ await client.beta.assistants.del(assistant.id);
750
+ } catch (cleanupError) {
751
+ console.warn('⚠️ Assistant cleanup failed:', cleanupError.message);
752
+ }
753
+
754
+ // Save test cases to file for easy reference
755
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
756
+ const outputFileName = `test_cases_${timestamp.substring(0, 19)}.md`;
757
+ const outputPath = path.join(path.dirname(pdfPath), outputFileName);
758
+
759
+ const fileContent = `# Manual Test Cases
760
+ Generated on: ${new Date().toLocaleString()}
761
+
762
+ ## Source Information
763
+ - **JIRA Summary:** ${jiraSummary}
764
+ - **JIRA Description:** ${jiraDescription}
765
+ - **Design File:** ${fileName}
766
+ - **Total Test Cases:** ${testCaseCount}
767
+
768
+ ---
769
+
770
+ ${testCases}
771
+
772
+ ---
773
+
774
+ ## Test Execution Notes
775
+ - Execute test cases in the order listed
776
+ - Log defects with screenshots when issues found
777
+ - Update test results in your test management tool
778
+ - Verify on multiple browsers/devices for compatibility tests
779
+ `;
780
+
781
+ await fs.writeFile(outputPath, fileContent, 'utf-8');
782
+
783
+ console.log(`✅ Test cases generated and saved!`);
594
784
 
595
785
  return {
596
- message: "✅ PDF uploaded to OpenAI successfully!",
597
- fileId: fileUpload.id,
598
- promptUsed: prompt
786
+ success: true,
787
+ message: `✅ Successfully generated ${testCaseCount} manual test cases!`,
788
+ results: {
789
+ testCases: testCases,
790
+ summary: {
791
+ totalTestCases: testCaseCount,
792
+ categories: [
793
+ "Functional Test Cases",
794
+ "UI/UX Test Cases",
795
+ "Edge Case & Negative Test Cases",
796
+ "Compatibility Test Cases",
797
+ "Accessibility Test Cases"
798
+ ],
799
+ generatedFrom: {
800
+ jiraSummary: jiraSummary,
801
+ designFile: fileName,
802
+ fileSize: `${(stats.size / 1024).toFixed(2)} KB`
803
+ }
804
+ },
805
+ outputs: {
806
+ savedToFile: outputPath,
807
+ fileName: outputFileName,
808
+ generatedAt: new Date().toISOString()
809
+ },
810
+ execution: {
811
+ processingTime: `${attempts} seconds`,
812
+ model: "gpt-4o",
813
+ pdfAnalyzed: true
814
+ }
815
+ }
599
816
  };
817
+
600
818
  } catch (err) {
601
- return `❌ Error uploading PDF to OpenAI: ${err.message}`;
819
+ console.error("❌ Test Case Generation Error:", err);
820
+
821
+ let errorMessage = "❌ Failed to generate test cases: ";
822
+
823
+ if (err.message?.includes('quota')) {
824
+ errorMessage += "OpenAI quota exceeded. Check billing.";
825
+ } else if (err.message?.includes('timeout')) {
826
+ errorMessage += "Generation took too long. Try with smaller PDF.";
827
+ } else if (err.message?.includes('file')) {
828
+ errorMessage += "PDF processing error. Check file format.";
829
+ } else if (err.message?.includes('API key')) {
830
+ errorMessage += "Invalid OpenAI API key.";
831
+ } else {
832
+ errorMessage += err.message || "Unknown error occurred.";
833
+ }
834
+
835
+ return {
836
+ success: false,
837
+ message: errorMessage,
838
+ error: {
839
+ message: err.message,
840
+ timestamp: new Date().toISOString(),
841
+ attempted: {
842
+ jiraSummary: jiraSummary,
843
+ pdfPath: pdfPath
844
+ }
845
+ },
846
+ troubleshooting: [
847
+ "Verify OpenAI API key in ~/Desktop/openai.json",
848
+ "Check PDF file exists and is readable",
849
+ "Ensure sufficient OpenAI credits",
850
+ "Try with smaller PDF if timeout occurs"
851
+ ]
852
+ };
602
853
  }
603
854
  }
604
855
  );
856
+
857
+ // ----------------------
858
+ // USAGE EXAMPLE
859
+ // ----------------------
860
+
861
+ /*
862
+ const result = await tools.uploadPdfToOpenai({
863
+ jiraSummary: "User Login Page Implementation",
864
+ jiraDescription: "Create a responsive login page with email/password fields, remember me checkbox, forgot password link, and social login options. Must support mobile and desktop views.",
865
+ pdfPath: "/path/to/login-design.pdf"
866
+ });
867
+
868
+ // Expected Output:
869
+ // - 15-25 detailed manual test cases
870
+ // - Categories: Functional, UI/UX, Edge Cases, Compatibility, Accessibility
871
+ // - Saved to markdown file for easy reference
872
+ // - Structured format ready for test management tools
873
+ */
874
+
605
875
  return server;
606
876
  };
607
877
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbakka/mcp-appium",
3
- "version": "2.0.52",
3
+ "version": "2.0.54",
4
4
  "description": "Appium MCP",
5
5
  "engines": {
6
6
  "node": ">=18"