@nbakka/mcp-appium 2.0.36 → 2.0.38
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/app_context.txt +91 -10
- package/lib/server.js +128 -0
- package/package.json +1 -1
package/lib/app_context.txt
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
******** START OF GUIDELINES ********
|
|
2
|
+
You are an expert Appium test case generator. You will be given a set of instructions to follow to reach a certain goal in the app. You have to generate Appium code based on the elements you find on the screen while navigating through the app.
|
|
2
3
|
When asked to open buy SRP, use deeplink to open https://housing.com/in/buy/mumbai/<locality name>. For example: if asked to open 'Mira Road East' buy SRP, then open this deeplink: https://housing.com/in/buy/mumbai/mira_road_east
|
|
3
4
|
Homepage deeplink -> https://housing.com/in/buy/real-estate-mumbai
|
|
4
5
|
Only use the locator data from the Google Sheet at the end while generating test cases, don't use to navigate through screens, always get current screen elements and navigate then at end use locator data to generate code, if xpath is already present in sheet then directly add xpath locatorName to code if not then add the required xpath in comments which will then be added to the sheet manually
|
|
@@ -7,19 +8,99 @@ Filters on the filter screen may initially be offscreen and require scrolling to
|
|
|
7
8
|
Stop execution if any step fails.
|
|
8
9
|
IMPORTANT: If the element you looking for is not present you can use swipe
|
|
9
10
|
(Remember this properly - Very important) After the execution is successful/completed, do NOT analyze what you found or did; only generate Appium code.
|
|
10
|
-
Print the Java/Appium code. In our framework, we use the following methods:
|
|
11
11
|
- waitForElementToBeVisible("locator_key", Wait.SHORT); locator_key is locator key and wait can be SHORT, MEDIUM, or LONG.
|
|
12
|
-
- w3CGestures.scrollUp(n)
|
|
13
|
-
- w3CGestures.scrollScreen("image_tour_navbar", ScrollDirection.MEDIUM_UP, 2); // image_tour_navbar is locator key, ScrollDirection can be SMALL_UP, MEDIUM_UP, LARGE_UP, SMALL_DOWN, MEDIUM_DOWN, LARGE_DOWN.
|
|
12
|
+
- w3CGestures.scrollUp(n); // n is the number of times to scroll. Up and Down in scroll methods are direction of finger movement.
|
|
13
|
+
- w3CGestures.scrollScreen("image_tour_navbar", ScrollDirection.MEDIUM_UP, 2); // image_tour_navbar is locator key, ScrollDirection can be SMALL_UP, MEDIUM_UP, LARGE_UP, SMALL_DOWN, MEDIUM_DOWN, LARGE_DOWN and 2 is max number of scrolls to perform, if not found in 2 scrolls then it will throw error. (DO NOT USE other methods except these two for scrolling)
|
|
14
14
|
- navigateBack(); // for back action.
|
|
15
15
|
- deepLink.OpenDeepLink("deeplink_key"); // deeplink_key is deeplink name.
|
|
16
16
|
- getText("locator_key"); // to get text of element.
|
|
17
|
-
Using the above, return code that looks like:
|
|
18
|
-
deepLink.OpenDeepLink(deeplink); // deeplink -> https://housing.com
|
|
19
|
-
waitForElementToBeVisible("view_on_map", Wait.LONG); // view_on_map=//android.widget.TextView[@text="View On Map"]; here xpath should be comments so it can be pasted in xpath sheet.
|
|
20
|
-
When giving output, please remove escape characters before double quotes.
|
|
21
|
-
Do similarly for click and sendKeys methods:
|
|
22
17
|
- click("search_select", Wait.SHORT)
|
|
23
18
|
- sendKeys("locator_key", "string_to_be_passed", Wait.SHORT)
|
|
24
19
|
Figure out dynamic paths like //android.widget.TextView[@text="View 180 Properties"] here 180 is not constant so create a xpath like //android.widget.TextView[contains(@text,"View") and contains(@text,"Properties")]
|
|
25
|
-
For repeating logic, create loops
|
|
20
|
+
For repeating logic, create loops
|
|
21
|
+
Generated code should be pasted in aicode.txt as mentioned below, clear the existing contents of it before copying new code.
|
|
22
|
+
No need to add comments in code and only mention locators that are new and not present in the locator sheet in comments at end of aicode.txt file as shown in sample code below.
|
|
23
|
+
** SAMPLE FILE aicode.txt **
|
|
24
|
+
/**
|
|
25
|
+
* Test Method for Nearby Properties Verification on New Project Dedicated Page
|
|
26
|
+
* Verifies that clicking on nearby properties opens the correct detail page
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
@Test(priority = 19, dataProvider = "test-data", dataProviderClass = TestDataProvider.class)
|
|
30
|
+
@Description("Test Description: Verify nearby properties functionality on new project dedicated page")
|
|
31
|
+
public void npNearbyPropertiesVerification(HashMap<String, String> testData) {
|
|
32
|
+
detailPage.npNearbyPropertiesVerification(testData.get("np_dedicated_deeplink"));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Method Definition for DetailPage.java
|
|
37
|
+
* Add this method to the DetailPage class
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
public void npNearbyPropertiesVerification(String deeplink) {
|
|
41
|
+
deepLink.OpenDeepLink(deeplink);
|
|
42
|
+
if (waitForElementToBePresent("later_btn", Wait.SHORT)) {
|
|
43
|
+
click("later_btn");
|
|
44
|
+
}
|
|
45
|
+
waitForElementToBeVisible("project_name", Wait.LONG);
|
|
46
|
+
waitForElementToBeVisible("Overview", Wait.SHORT);
|
|
47
|
+
w3CGestures.scrollScreen("high_value_project_title", ScrollDirection.MEDIUM_UP, 2);
|
|
48
|
+
waitForElementToBeVisible("high_value_project_title", Wait.SHORT);
|
|
49
|
+
String nearbyProjectsTitle = getElement("high_value_project_title").getText();
|
|
50
|
+
Assert.assertTrue(nearbyProjectsTitle.contains("Premium projects nearby"),
|
|
51
|
+
"Expected nearby projects section title to contain 'Premium projects nearby' but found: " + nearbyProjectsTitle);
|
|
52
|
+
w3CGestures.scrollScreen("near_by_project_name1", ScrollDirection.MEDIUM_UP, 2);
|
|
53
|
+
waitForElementToBeVisible("near_by_project_name1", Wait.SHORT);
|
|
54
|
+
waitForElementToBeVisible("near_by_project_price1", Wait.SHORT);
|
|
55
|
+
String expectedPropertyName = getElement("near_by_project_name1").getText();
|
|
56
|
+
String expectedPropertyPrice = getElement("near_by_project_price1").getText();
|
|
57
|
+
System.out.println("Expected Property Name: " + expectedPropertyName);
|
|
58
|
+
System.out.println("Expected Property Price: " + expectedPropertyPrice);
|
|
59
|
+
click("nearby_high_value_project1");
|
|
60
|
+
waitForElementToBeVisible("project_name", Wait.LONG);
|
|
61
|
+
waitForElementToBeVisible("project_price", Wait.SHORT);
|
|
62
|
+
String actualPropertyName = getElement("project_name").getText();
|
|
63
|
+
String actualPropertyPrice = getElement("project_price").getText();
|
|
64
|
+
System.out.println("Actual Property Name: " + actualPropertyName);
|
|
65
|
+
System.out.println("Actual Property Price: " + actualPropertyPrice);
|
|
66
|
+
Assert.assertEquals(actualPropertyName, expectedPropertyName,
|
|
67
|
+
"Property name mismatch. Expected: " + expectedPropertyName + " but found: " + actualPropertyName);
|
|
68
|
+
Assert.assertEquals(actualPropertyPrice, expectedPropertyPrice,
|
|
69
|
+
"Property price mismatch. Expected: " + expectedPropertyPrice + " but found: " + actualPropertyPrice);
|
|
70
|
+
waitForElementToBeVisible("Overview", Wait.SHORT);
|
|
71
|
+
waitForElementToBeVisible("Highlights", Wait.SHORT);
|
|
72
|
+
waitForElementToBeVisible("Property Tour", Wait.SHORT);
|
|
73
|
+
System.out.println("✓ Nearby properties verification completed successfully");
|
|
74
|
+
System.out.println("✓ Property '" + expectedPropertyName + "' opened correctly with matching price: " + expectedPropertyPrice);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Additional Locators to be added to the locator sheet or properties file:
|
|
79
|
+
*
|
|
80
|
+
* # Nearby Projects Section Locators
|
|
81
|
+
* high_value_project_title=//android.widget.TextView[@text[contains(.,'Premium projects nearby')]]
|
|
82
|
+
* high_value_project_section=//android.view.View[@content-desc='high_value_project_section']
|
|
83
|
+
* near_by_project_name1=//android.widget.TextView[@content-desc='near_by_project_name1']
|
|
84
|
+
* near_by_project_price1=//android.widget.TextView[@content-desc='near_by_project_price1']
|
|
85
|
+
* near_by_project_name2=//android.widget.TextView[@content-desc='near_by_project_name2']
|
|
86
|
+
* near_by_project_price2=//android.widget.TextView[@content-desc='near_by_project_price2']
|
|
87
|
+
* near_by_project_name3=//android.widget.TextView[@content-desc='near_by_project_name3']
|
|
88
|
+
* near_by_project_price3=//android.widget.TextView[@content-desc='near_by_project_price3']
|
|
89
|
+
* near_by_project_name4=//android.widget.TextView[@content-desc='near_by_project_name4']
|
|
90
|
+
* near_by_project_price4=//android.widget.TextView[@content-desc='near_by_project_price4']
|
|
91
|
+
* nearby_high_value_project1=//android.view.View[@content-desc='nearby_high_value_project1']
|
|
92
|
+
* nearby_high_value_project2=//android.view.View[@content-desc='nearby_high_value_project2']
|
|
93
|
+
* nearby_high_value_project3=//android.view.View[@content-desc='nearby_high_value_project3']
|
|
94
|
+
* nearby_high_value_project4=//android.view.View[@content-desc='nearby_high_value_project4']
|
|
95
|
+
* project_name=//android.widget.TextView[@content-desc='project_name']
|
|
96
|
+
* project_price=//android.widget.TextView[@content-desc='project_price']
|
|
97
|
+
* project_address=//android.widget.TextView[@content-desc='project_address']
|
|
98
|
+
* project_config=//android.widget.TextView[@content-desc='project_config']
|
|
99
|
+
* later_btn=//android.widget.TextView[@text='Later']
|
|
100
|
+
* Overview=//android.widget.TextView[@text='Overview']
|
|
101
|
+
* Highlights=//android.widget.TextView[@text='Highlights']
|
|
102
|
+
* Property Tour=//android.widget.TextView[@text='Property Tour']
|
|
103
|
+
* Data Insights=//android.widget.TextView[@text='Data Insights']
|
|
104
|
+
|
|
105
|
+
** END OF SAMPLE FILE **
|
|
106
|
+
******** END OF GUIDELINES ********
|
package/lib/server.js
CHANGED
|
@@ -14,6 +14,7 @@ const ios_1 = require("./ios");
|
|
|
14
14
|
const png_1 = require("./png");
|
|
15
15
|
const image_utils_1 = require("./image-utils");
|
|
16
16
|
const { google } = require('googleapis');
|
|
17
|
+
const axios = require('axios');
|
|
17
18
|
const getAgentVersion = () => {
|
|
18
19
|
const json = require("../package.json");
|
|
19
20
|
return json.version;
|
|
@@ -316,6 +317,133 @@ const createMcpServer = () => {
|
|
|
316
317
|
}
|
|
317
318
|
);
|
|
318
319
|
|
|
320
|
+
tool(
|
|
321
|
+
"mobile_fetch_jira_ticket",
|
|
322
|
+
"Fetch JIRA ticket information including summary, description, and extract Figma links from description",
|
|
323
|
+
{
|
|
324
|
+
ticketId: zod_1.z.string().describe("The JIRA ticket ID (e.g., HDA-434)"),
|
|
325
|
+
},
|
|
326
|
+
async ({ ticketId }) => {
|
|
327
|
+
try {
|
|
328
|
+
// Read JIRA credentials from desktop/jira.json file
|
|
329
|
+
const jiraConfigPath = path.join(os.homedir(), 'Desktop', 'jira.json');
|
|
330
|
+
|
|
331
|
+
let jiraConfig;
|
|
332
|
+
try {
|
|
333
|
+
const configContent = await fs.readFile(jiraConfigPath, 'utf-8');
|
|
334
|
+
jiraConfig = JSON.parse(configContent);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
throw new Error(`Failed to read JIRA config from ${jiraConfigPath}: ${error.message}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Extract all required values from JSON file
|
|
340
|
+
const { api: jiraApiToken, baseUrl: jiraBaseUrl, email: jiraEmail } = jiraConfig;
|
|
341
|
+
|
|
342
|
+
if (!jiraApiToken) {
|
|
343
|
+
throw new Error('JIRA API token not found in jira.json file. Please ensure the file contains "api" field.');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (!jiraBaseUrl) {
|
|
347
|
+
throw new Error('JIRA base URL not found in jira.json file. Please ensure the file contains "baseUrl" field.');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!jiraEmail) {
|
|
351
|
+
throw new Error('JIRA email not found in jira.json file. Please ensure the file contains "email" field.');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Create Basic Auth token
|
|
355
|
+
const auth = Buffer.from(`${jiraEmail}:${jiraApiToken}`).toString('base64');
|
|
356
|
+
|
|
357
|
+
// Fetch ticket from JIRA API
|
|
358
|
+
const response = await axios.get(
|
|
359
|
+
`${jiraBaseUrl}/rest/api/3/issue/${ticketId}`,
|
|
360
|
+
{
|
|
361
|
+
headers: {
|
|
362
|
+
'Authorization': `Basic ${auth}`,
|
|
363
|
+
'Accept': 'application/json',
|
|
364
|
+
'Content-Type': 'application/json'
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
const issue = response.data;
|
|
370
|
+
|
|
371
|
+
// Extract summary and description
|
|
372
|
+
const summary = issue.fields.summary || 'No summary available';
|
|
373
|
+
const description = issue.fields.description ?
|
|
374
|
+
extractTextFromADF(issue.fields.description) :
|
|
375
|
+
'No description available';
|
|
376
|
+
|
|
377
|
+
// Extract Figma links from description
|
|
378
|
+
const figmaLinks = extractFigmaLinks(description);
|
|
379
|
+
|
|
380
|
+
// Format response
|
|
381
|
+
const result = {
|
|
382
|
+
ticketId: ticketId,
|
|
383
|
+
summary: summary,
|
|
384
|
+
description: description,
|
|
385
|
+
figmaLinks: figmaLinks.length > 0 ? figmaLinks : ['No Figma links found']
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
return `JIRA Ticket Information:
|
|
389
|
+
Ticket ID: ${result.ticketId}
|
|
390
|
+
Summary: ${result.summary}
|
|
391
|
+
Description: ${result.description}
|
|
392
|
+
Figma Links: ${result.figmaLinks.join(', ')}`;
|
|
393
|
+
|
|
394
|
+
} catch (error) {
|
|
395
|
+
if (error.response && error.response.status === 404) {
|
|
396
|
+
return `Error: JIRA ticket ${ticketId} not found. Please check the ticket ID.`;
|
|
397
|
+
} else if (error.response && error.response.status === 401) {
|
|
398
|
+
return `Error: Authentication failed. Please check your JIRA credentials.`;
|
|
399
|
+
} else {
|
|
400
|
+
return `Error fetching JIRA ticket: ${error.message}`;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
// Helper function to extract text from Atlassian Document Format (ADF)
|
|
407
|
+
function extractTextFromADF(adfContent) {
|
|
408
|
+
if (!adfContent || typeof adfContent !== 'object') {
|
|
409
|
+
return String(adfContent || '');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
let text = '';
|
|
413
|
+
|
|
414
|
+
function traverse(node) {
|
|
415
|
+
if (node.type === 'text') {
|
|
416
|
+
text += node.text || '';
|
|
417
|
+
} else if (node.content && Array.isArray(node.content)) {
|
|
418
|
+
node.content.forEach(traverse);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Add line breaks for paragraphs
|
|
422
|
+
if (node.type === 'paragraph') {
|
|
423
|
+
text += '\n';
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (adfContent.content) {
|
|
428
|
+
adfContent.content.forEach(traverse);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return text.trim();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Helper function to extract Figma links from text
|
|
435
|
+
function extractFigmaLinks(text) {
|
|
436
|
+
if (!text || typeof text !== 'string') {
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Regular expression to match Figma URLs
|
|
441
|
+
const figmaRegex = /https?:\/\/(?:www\.)?figma\.com\/[^\s<>"{}|\\^`\[\]]+/gi;
|
|
442
|
+
const matches = text.match(figmaRegex);
|
|
443
|
+
|
|
444
|
+
return matches ? [...new Set(matches)] : []; // Remove duplicates
|
|
445
|
+
}
|
|
446
|
+
|
|
319
447
|
return server;
|
|
320
448
|
};
|
|
321
449
|
|