appium-mcp 1.8.1 → 1.8.3

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 (88) hide show
  1. package/.prettierrc +1 -2
  2. package/CHANGELOG.md +12 -0
  3. package/dist/devicemanager/ios-manager.js +2 -2
  4. package/dist/devicemanager/ios-manager.js.map +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/locators/element-filter.d.ts.map +1 -1
  8. package/dist/locators/element-filter.js +4 -3
  9. package/dist/locators/element-filter.js.map +1 -1
  10. package/dist/locators/generate-all-locators.js +1 -1
  11. package/dist/locators/generate-all-locators.js.map +1 -1
  12. package/dist/locators/locator-generation.js +11 -11
  13. package/dist/locators/locator-generation.js.map +1 -1
  14. package/dist/server.js +1 -1
  15. package/dist/server.js.map +1 -1
  16. package/dist/session-store.d.ts.map +1 -1
  17. package/dist/session-store.js +4 -2
  18. package/dist/session-store.js.map +1 -1
  19. package/dist/tests/__mocks__/@appium/support.d.ts.map +1 -1
  20. package/dist/tests/__mocks__/@appium/support.js +20 -21
  21. package/dist/tests/__mocks__/@appium/support.js.map +1 -1
  22. package/dist/tests/generate-all-locators.test.js +6 -10
  23. package/dist/tests/generate-all-locators.test.js.map +1 -1
  24. package/dist/tests/test-setup-wda.js +26 -16
  25. package/dist/tests/test-setup-wda.js.map +1 -1
  26. package/dist/tools/documentation/reasoning-rag.js +9 -9
  27. package/dist/tools/documentation/reasoning-rag.js.map +1 -1
  28. package/dist/tools/documentation/simple-pdf-indexer.js +14 -14
  29. package/dist/tools/documentation/simple-pdf-indexer.js.map +1 -1
  30. package/dist/tools/index.js +1 -1
  31. package/dist/tools/index.js.map +1 -1
  32. package/dist/tools/interactions/drag-and-drop.js +1 -1
  33. package/dist/tools/interactions/drag-and-drop.js.map +1 -1
  34. package/dist/tools/interactions/long-press.js +2 -2
  35. package/dist/tools/interactions/long-press.js.map +1 -1
  36. package/dist/tools/ios/boot-simulator.js +1 -1
  37. package/dist/tools/ios/boot-simulator.js.map +1 -1
  38. package/dist/tools/ios/install-wda.js +3 -4
  39. package/dist/tools/ios/install-wda.js.map +1 -1
  40. package/dist/tools/ios/setup-wda.d.ts.map +1 -1
  41. package/dist/tools/ios/setup-wda.js +2 -3
  42. package/dist/tools/ios/setup-wda.js.map +1 -1
  43. package/dist/tools/navigations/scroll-to-element.d.ts.map +1 -1
  44. package/dist/tools/navigations/scroll-to-element.js +16 -9
  45. package/dist/tools/navigations/scroll-to-element.js.map +1 -1
  46. package/dist/tools/navigations/scroll.js +6 -6
  47. package/dist/tools/navigations/scroll.js.map +1 -1
  48. package/dist/tools/navigations/swipe.js +2 -2
  49. package/dist/tools/navigations/swipe.js.map +1 -1
  50. package/dist/tools/session/create-session.js +1 -1
  51. package/dist/tools/session/create-session.js.map +1 -1
  52. package/dist/tools/session/select-device.js +4 -4
  53. package/dist/tools/session/select-device.js.map +1 -1
  54. package/dist/tools/test-generation/generate-tests.d.ts.map +1 -1
  55. package/dist/tools/test-generation/generate-tests.js +10 -12
  56. package/dist/tools/test-generation/generate-tests.js.map +1 -1
  57. package/dist/ui/mcp-ui-utils.js +28 -28
  58. package/dist/ui/mcp-ui-utils.js.map +1 -1
  59. package/eslint.config.js +6 -32
  60. package/package.json +3 -6
  61. package/server.json +2 -2
  62. package/src/devicemanager/ios-manager.ts +2 -2
  63. package/src/index.ts +1 -1
  64. package/src/locators/element-filter.ts +5 -3
  65. package/src/locators/generate-all-locators.ts +1 -1
  66. package/src/locators/locator-generation.ts +11 -11
  67. package/src/server.ts +2 -2
  68. package/src/session-store.ts +6 -2
  69. package/src/tests/__mocks__/@appium/support.ts +3 -4
  70. package/src/tests/generate-all-locators.test.ts +12 -13
  71. package/src/tests/test-setup-wda.ts +24 -16
  72. package/src/tools/documentation/reasoning-rag.ts +10 -10
  73. package/src/tools/documentation/simple-pdf-indexer.ts +23 -21
  74. package/src/tools/index.ts +1 -1
  75. package/src/tools/interactions/drag-and-drop.ts +1 -1
  76. package/src/tools/interactions/long-press.ts +2 -2
  77. package/src/tools/ios/boot-simulator.ts +1 -1
  78. package/src/tools/ios/install-wda.ts +4 -4
  79. package/src/tools/ios/setup-wda.ts +2 -3
  80. package/src/tools/navigations/scroll-to-element.ts +19 -9
  81. package/src/tools/navigations/scroll.ts +6 -6
  82. package/src/tools/navigations/swipe.ts +2 -2
  83. package/src/tools/session/create-session.ts +1 -1
  84. package/src/tools/session/select-device.ts +4 -4
  85. package/src/tools/test-generation/generate-tests.ts +10 -12
  86. package/src/types/appium-xcuitest-driver.d.ts +1 -1
  87. package/src/ui/mcp-ui-utils.ts +28 -28
  88. package/tsconfig.tsbuildinfo +1 -1
@@ -39,9 +39,11 @@ function matchesAttributeFilters(
39
39
  ): boolean {
40
40
  if (requireAttributes.length > 0) {
41
41
  const hasRequiredAttr = requireAttributes.some(
42
- attr => element.attributes && element.attributes[attr]
42
+ (attr) => element.attributes && element.attributes[attr]
43
43
  );
44
- if (!hasRequiredAttr) return false;
44
+ if (!hasRequiredAttr) {
45
+ return false;
46
+ }
45
47
  }
46
48
 
47
49
  if (
@@ -87,7 +89,7 @@ function isInteractableElement(
87
89
  ];
88
90
 
89
91
  return (
90
- interactableTags.some(tag => element.tagName.includes(tag)) ||
92
+ interactableTags.some((tag) => element.tagName.includes(tag)) ||
91
93
  element.attributes?.clickable === 'true' ||
92
94
  element.attributes?.focusable === 'true'
93
95
  );
@@ -104,7 +104,7 @@ function traverseAndProcessElements(
104
104
 
105
105
  // Recursively process children (even if parent was filtered out)
106
106
  if (element.children && element.children.length > 0) {
107
- element.children.forEach(child =>
107
+ element.children.forEach((child) =>
108
108
  traverseAndProcessElements(
109
109
  child,
110
110
  sourceXML,
@@ -82,7 +82,7 @@ export function getSimpleSuggestedLocators(
82
82
  isNative: boolean = true
83
83
  ): Record<string, string> {
84
84
  const res: Record<string, string> = {};
85
- for (let [strategyAlias, strategy] of SIMPLE_STRATEGY_MAPPINGS) {
85
+ for (const [strategyAlias, strategy] of SIMPLE_STRATEGY_MAPPINGS) {
86
86
  // accessibility id is only supported in native context
87
87
  if (!(strategy === 'accessibility id' && !isNative)) {
88
88
  const value = attributes[strategyAlias];
@@ -103,7 +103,7 @@ export function getComplexSuggestedLocators(
103
103
  isNative: boolean,
104
104
  automationName: string
105
105
  ): Record<string, string> {
106
- let complexLocators: Record<string, string | null> = {};
106
+ const complexLocators: Record<string, string | null> = {};
107
107
  const domNode = findDOMNodeByPath(path, sourceDoc);
108
108
  if (isNative) {
109
109
  switch (automationName) {
@@ -342,7 +342,7 @@ export function getOptimalXPath(
342
342
  ];
343
343
  const attrPairsPermutations: [string, string][] = attrsForPairs.flatMap(
344
344
  (v1, i) =>
345
- attrsForPairs.slice(i + 1).map(v2 => [v1, v2] as [string, string])
345
+ attrsForPairs.slice(i + 1).map((v2) => [v1, v2] as [string, string])
346
346
  );
347
347
 
348
348
  const cases = [
@@ -405,7 +405,7 @@ export function getOptimalXPath(
405
405
 
406
406
  // If there's more than one sibling, append the index
407
407
  if (childNodes.length > 1) {
408
- let index = childNodes.indexOf(domNode);
408
+ const index = childNodes.indexOf(domNode);
409
409
  xpath += `[${index + 1}]`;
410
410
  }
411
411
  }
@@ -440,7 +440,7 @@ export function getOptimalClassChain(
440
440
  // BASE CASE #2: If this node has a unique class chain based on attributes, return it
441
441
  let classChain: string, othersWithAttr: XMLNode[];
442
442
 
443
- for (let attrName of CHECKED_CLASS_CHAIN_ATTRIBUTES) {
443
+ for (const attrName of CHECKED_CLASS_CHAIN_ATTRIBUTES) {
444
444
  const attrValue = (domNode as XMLElement).getAttribute?.(attrName);
445
445
  if (_.isEmpty(attrValue)) {
446
446
  continue;
@@ -462,7 +462,7 @@ export function getOptimalClassChain(
462
462
 
463
463
  // If the attribute isn't actually unique, get its index too
464
464
  if (othersWithAttr.length > 1) {
465
- let index = othersWithAttr.indexOf(domNode);
465
+ const index = othersWithAttr.indexOf(domNode);
466
466
  classChain = `${classChain}[${index + 1}]`;
467
467
  }
468
468
  return classChain;
@@ -484,7 +484,7 @@ export function getOptimalClassChain(
484
484
 
485
485
  // If there's more than one sibling, append the index
486
486
  if (childNodes.length > 1) {
487
- let index = childNodes.indexOf(domNode);
487
+ const index = childNodes.indexOf(domNode);
488
488
  classChain += `[${index + 1}]`;
489
489
  }
490
490
  }
@@ -513,11 +513,11 @@ export function getOptimalPredicateString(
513
513
  }
514
514
 
515
515
  // BASE CASE #2: Check all attributes and try to find the best way
516
- let xpathAttributes: string[] = [];
517
- let predicateString: string[] = [];
516
+ const xpathAttributes: string[] = [];
517
+ const predicateString: string[] = [];
518
518
  let othersWithAttr: XMLNode[];
519
519
 
520
- for (let attrName of CHECKED_PREDICATE_ATTRIBUTES) {
520
+ for (const attrName of CHECKED_PREDICATE_ATTRIBUTES) {
521
521
  const attrValue = (domNode as XMLElement).getAttribute?.(attrName);
522
522
  if (_.isEmpty(attrValue)) {
523
523
  continue;
@@ -579,7 +579,7 @@ export function getOptimalUiAutomatorSelector(
579
579
  // BASE CASE #3: If looking for an element that is not inside
580
580
  // the last direct child of the hierarchy, return null
581
581
  const lastHierarchyChildIndex = (hierarchyChildren.length - 1).toString();
582
- let pathArray = path.split('.');
582
+ const pathArray = path.split('.');
583
583
  const requestedHierarchyChildIndex = pathArray[0];
584
584
  if (requestedHierarchyChildIndex !== lastHierarchyChildIndex) {
585
585
  return null;
package/src/server.ts CHANGED
@@ -15,11 +15,11 @@ registerResources(server);
15
15
  registerTools(server);
16
16
 
17
17
  // Handle client connection and disconnection events
18
- server.on('connect', event => {
18
+ server.on('connect', (event) => {
19
19
  log.info('Client connected:', event.session);
20
20
  });
21
21
 
22
- server.on('disconnect', async event => {
22
+ server.on('disconnect', async (event) => {
23
23
  log.info('Client disconnected:', event.session);
24
24
  // Only try to clean up if there's an active session
25
25
  if (hasActiveSession()) {
@@ -97,8 +97,12 @@ export async function safeDeleteSession(): Promise<boolean> {
97
97
  }
98
98
 
99
99
  export const getPlatformName = (driver: any): string => {
100
- if (driver instanceof AndroidUiautomator2Driver) return PLATFORM.android;
101
- if (driver instanceof XCUITestDriver) return PLATFORM.ios;
100
+ if (driver instanceof AndroidUiautomator2Driver) {
101
+ return PLATFORM.android;
102
+ }
103
+ if (driver instanceof XCUITestDriver) {
104
+ return PLATFORM.ios;
105
+ }
102
106
 
103
107
  if ((driver as Client).isAndroid) {
104
108
  return PLATFORM.android;
@@ -2,10 +2,10 @@
2
2
  // This avoids the ESM/CommonJS mismatch with uuid dependency
3
3
 
4
4
  export const logger = {
5
- getLogger: (name: string) => {
5
+ getLogger: (name: string) =>
6
6
  // Simple logger implementation for tests
7
7
  // No-op functions that match the logger interface
8
- return {
8
+ ({
9
9
  debug: (message: string, ...args: any[]) => {
10
10
  // Silent in tests by default
11
11
  },
@@ -21,8 +21,7 @@ export const logger = {
21
21
  trace: (message: string, ...args: any[]) => {
22
22
  // Silent in tests by default
23
23
  },
24
- };
25
- },
24
+ }),
26
25
  };
27
26
 
28
27
  // Export other commonly used utilities from @appium/support if needed
@@ -75,9 +75,9 @@ describe('generateAllElementLocators', () => {
75
75
  // If the filter works, either the result will be empty (if no buttons found)
76
76
  // or all elements will be buttons
77
77
  if (result.length > 0) {
78
- expect(result.every(element => element.tagName.includes('Button'))).toBe(
79
- true
80
- );
78
+ expect(
79
+ result.every((element) => element.tagName.includes('Button'))
80
+ ).toBe(true);
81
81
  }
82
82
  });
83
83
 
@@ -88,7 +88,7 @@ describe('generateAllElementLocators', () => {
88
88
  });
89
89
 
90
90
  // Verify no Button elements are included
91
- expect(result.every(element => !element.tagName.includes('Button'))).toBe(
91
+ expect(result.every((element) => !element.tagName.includes('Button'))).toBe(
92
92
  true
93
93
  );
94
94
  });
@@ -129,12 +129,11 @@ describe('generateAllElementLocators', () => {
129
129
  ];
130
130
 
131
131
  expect(
132
- result.every(element => {
133
- return (
134
- interactableTags.some(tag => element.tagName.includes(tag)) ||
132
+ result.every(
133
+ (element) =>
134
+ interactableTags.some((tag) => element.tagName.includes(tag)) ||
135
135
  element.clickable === true
136
- );
137
- })
136
+ )
138
137
  ).toBe(true);
139
138
  }
140
139
  });
@@ -160,9 +159,9 @@ describe('generateAllElementLocators', () => {
160
159
  ];
161
160
 
162
161
  expect(
163
- result.every(element => {
164
- return interactableTags.some(tag => element.tagName.includes(tag));
165
- })
162
+ result.every((element) =>
163
+ interactableTags.some((tag) => element.tagName.includes(tag))
164
+ )
166
165
  ).toBe(true);
167
166
  }
168
167
  });
@@ -174,6 +173,6 @@ describe('generateAllElementLocators', () => {
174
173
  });
175
174
 
176
175
  // Verify all elements are clickable
177
- expect(result.every(element => element.clickable === true)).toBe(true);
176
+ expect(result.every((element) => element.clickable === true)).toBe(true);
178
177
  });
179
178
  });
@@ -31,14 +31,18 @@ async function downloadFile(url: string, destPath: string): Promise<void> {
31
31
  const file = fs.createWriteStream(destPath);
32
32
 
33
33
  https
34
- .get(url, response => {
34
+ .get(url, async (response) => {
35
35
  // Handle redirects
36
36
  if (response.statusCode === 302 || response.statusCode === 301) {
37
37
  file.close();
38
38
  fs.unlinkSync(destPath);
39
- return downloadFile(response.headers.location!, destPath)
40
- .then(resolve)
41
- .catch(reject);
39
+ try {
40
+ await downloadFile(response.headers.location!, destPath);
41
+ resolve();
42
+ } catch (err) {
43
+ reject(err);
44
+ }
45
+ return;
42
46
  }
43
47
 
44
48
  if (response.statusCode !== 200) {
@@ -55,7 +59,7 @@ async function downloadFile(url: string, destPath: string): Promise<void> {
55
59
  );
56
60
  let downloadedSize = 0;
57
61
 
58
- response.on('data', chunk => {
62
+ response.on('data', (chunk) => {
59
63
  downloadedSize += chunk.length;
60
64
  const percent = ((downloadedSize / totalSize) * 100).toFixed(1);
61
65
  process.stdout.write(
@@ -71,7 +75,7 @@ async function downloadFile(url: string, destPath: string): Promise<void> {
71
75
  resolve();
72
76
  });
73
77
  })
74
- .on('error', err => {
78
+ .on('error', (err) => {
75
79
  file.close();
76
80
  fs.unlinkSync(destPath);
77
81
  reject(err);
@@ -102,10 +106,10 @@ async function getLatestWDAVersion(): Promise<string> {
102
106
  };
103
107
 
104
108
  https
105
- .get(options, response => {
109
+ .get(options, (response) => {
106
110
  let data = '';
107
111
 
108
- response.on('data', chunk => {
112
+ response.on('data', (chunk) => {
109
113
  data += chunk;
110
114
  });
111
115
 
@@ -124,7 +128,7 @@ async function getLatestWDAVersion(): Promise<string> {
124
128
  }
125
129
  });
126
130
  })
127
- .on('error', err => {
131
+ .on('error', (err) => {
128
132
  reject(err);
129
133
  });
130
134
  });
@@ -163,7 +167,7 @@ async function main() {
163
167
  // Show app contents
164
168
  const appContents = fs.readdirSync(appPath);
165
169
  console.log('\n📋 Cached App bundle contents:');
166
- appContents.forEach(item => {
170
+ appContents.forEach((item) => {
167
171
  console.log(` - ${item}`);
168
172
  });
169
173
 
@@ -204,7 +208,7 @@ async function main() {
204
208
  // List contents
205
209
  console.log('\n📋 Extracted contents:');
206
210
  const contents = fs.readdirSync(extractDir);
207
- contents.forEach(item => {
211
+ contents.forEach((item) => {
208
212
  const itemPath = path.join(extractDir, item);
209
213
  const isDir = fs.statSync(itemPath).isDirectory();
210
214
  console.log(` ${isDir ? '📁' : '📄'} ${item}`);
@@ -218,7 +222,7 @@ async function main() {
218
222
  // Show app contents
219
223
  const appContents = fs.readdirSync(appPath);
220
224
  console.log('\n App bundle contents:');
221
- appContents.forEach(item => {
225
+ appContents.forEach((item) => {
222
226
  console.log(` - ${item}`);
223
227
  });
224
228
  } else {
@@ -241,7 +245,11 @@ async function main() {
241
245
  }
242
246
 
243
247
  // Run main function
244
- main().catch(error => {
245
- console.error('\n💥 Unexpected error:', error);
246
- process.exit(1);
247
- });
248
+ (async () => {
249
+ try {
250
+ await main();
251
+ } catch (error: any) {
252
+ console.error('\n💥 Unexpected error:', error);
253
+ process.exit(1);
254
+ }
255
+ })();
@@ -220,8 +220,8 @@ export class ReasoningRAG {
220
220
  for (let i = 0; i < chunks.length; i += batchSize) {
221
221
  const batch = chunks.slice(i, i + batchSize);
222
222
 
223
- const batchPromises = batch.flatMap(chunk =>
224
- configs.map(config =>
223
+ const batchPromises = batch.flatMap((chunk) =>
224
+ configs.map((config) =>
225
225
  this.performReasoning(chunk.pageContent, config, query)
226
226
  )
227
227
  );
@@ -249,16 +249,16 @@ export class ReasoningRAG {
249
249
  ): Promise<string> {
250
250
  // Extract all reasoning outputs
251
251
  const summaries = reasoningResults
252
- .filter(result => result.metadata.task === 'summarization')
253
- .map(result => result.reasoningOutput);
252
+ .filter((result) => result.metadata.task === 'summarization')
253
+ .map((result) => result.reasoningOutput);
254
254
 
255
255
  const analyses = reasoningResults
256
- .filter(result => result.metadata.task === 'analysis')
257
- .map(result => result.reasoningOutput);
256
+ .filter((result) => result.metadata.task === 'analysis')
257
+ .map((result) => result.reasoningOutput);
258
258
 
259
259
  const qaResults = reasoningResults
260
- .filter(result => result.metadata.task === 'question-answering')
261
- .map(result => result.reasoningOutput);
260
+ .filter((result) => result.metadata.task === 'question-answering')
261
+ .map((result) => result.reasoningOutput);
262
262
 
263
263
  // Combine all insights
264
264
  let comprehensiveSummary = `## Query: ${query}\n\n`;
@@ -332,7 +332,7 @@ export class ReasoningRAG {
332
332
  ];
333
333
 
334
334
  // Filter configs based on requested tasks
335
- const filteredConfigs = configs.filter(config =>
335
+ const filteredConfigs = configs.filter((config) =>
336
336
  reasoningTasks.includes(config.task)
337
337
  );
338
338
 
@@ -355,7 +355,7 @@ export class ReasoningRAG {
355
355
 
356
356
  // Step 5: Extract best answer from reasoning results
357
357
  const qaResults = reasoningResults.filter(
358
- result =>
358
+ (result) =>
359
359
  result.metadata.task === 'question-answering' &&
360
360
  !result.metadata.error
361
361
  );
@@ -72,7 +72,7 @@ async function saveDocuments(
72
72
  }
73
73
 
74
74
  // Serialize the new documents
75
- const serializedNew = documents.map(doc => ({
75
+ const serializedNew = documents.map((doc) => ({
76
76
  pageContent: doc.pageContent,
77
77
  metadata: doc.metadata,
78
78
  }));
@@ -357,12 +357,12 @@ export async function indexAllMarkdownFiles(
357
357
  // Add file metadata to each document
358
358
  const filename = path.basename(markdownFile);
359
359
  const relativePath = path.relative(dirPath, markdownFile);
360
- documents.forEach(doc => {
360
+ documents.forEach((doc) => {
361
361
  doc.metadata = {
362
362
  ...doc.metadata,
363
363
  source: markdownFile,
364
- filename: filename,
365
- relativePath: relativePath,
364
+ filename,
365
+ relativePath,
366
366
  };
367
367
  });
368
368
 
@@ -482,25 +482,27 @@ if (import.meta.url === `file://${process.argv[1]}`) {
482
482
  if (indexSingleFile) {
483
483
  // Index a single Markdown file
484
484
  log.info(`Indexing single Markdown file: ${markdownPath}`);
485
- indexMarkdown(markdownPath, chunkSize, chunkOverlap)
486
- .then(() => {
487
- process.exit(0);
488
- })
489
- .catch(error => {
490
- log.error('Indexing failed:', error);
491
- process.exit(1);
492
- });
485
+ try {
486
+ await indexMarkdown(markdownPath, chunkSize, chunkOverlap);
487
+ process.exit(0);
488
+ } catch (error) {
489
+ log.error('Indexing failed:', error);
490
+ process.exit(1);
491
+ }
493
492
  } else {
494
493
  // Index all Markdown files in the directory
495
494
  log.info(`Indexing all Markdown files in directory: ${markdownPath}`);
496
- indexAllMarkdownFiles(markdownPath, chunkSize, chunkOverlap)
497
- .then(indexedFiles => {
498
- log.info(`Successfully indexed ${indexedFiles.length} Markdown files`);
499
- process.exit(0);
500
- })
501
- .catch(error => {
502
- log.error('Indexing failed:', error);
503
- process.exit(1);
504
- });
495
+ try {
496
+ const indexedFiles = await indexAllMarkdownFiles(
497
+ markdownPath,
498
+ chunkSize,
499
+ chunkOverlap
500
+ );
501
+ log.info(`Successfully indexed ${indexedFiles.length} Markdown files`);
502
+ process.exit(0);
503
+ } catch (error) {
504
+ log.error('Indexing failed:', error);
505
+ process.exit(1);
506
+ }
505
507
  }
506
508
  }
@@ -69,7 +69,7 @@ export default function registerTools(server: FastMCP): void {
69
69
  JSON.stringify(obj, (key, value) => {
70
70
  if (
71
71
  key &&
72
- SENSITIVE_KEYS.some(k => key.toLowerCase().includes(k))
72
+ SENSITIVE_KEYS.some((k) => key.toLowerCase().includes(k))
73
73
  ) {
74
74
  return '[REDACTED]';
75
75
  }
@@ -28,7 +28,7 @@ async function performDragAndDrop(
28
28
  { type: 'pointerMove', duration: 0, x: startX, y: startY },
29
29
  { type: 'pointerDown', button: 0 },
30
30
  { type: 'pause', duration: longPressDuration },
31
- { type: 'pointerMove', duration: duration, x: endX, y: endY },
31
+ { type: 'pointerMove', duration, x: endX, y: endY },
32
32
  { type: 'pause', duration: DROP_PAUSE_DURATION_MS },
33
33
  { type: 'pointerUp', button: 0 },
34
34
  ],
@@ -54,7 +54,7 @@ export default function longPress(server: FastMCP): void {
54
54
  actions: [
55
55
  { type: 'pointerMove', duration: 0, x, y },
56
56
  { type: 'pointerDown', button: 0 },
57
- { type: 'pause', duration: duration },
57
+ { type: 'pause', duration },
58
58
  { type: 'pointerUp', button: 0 },
59
59
  ],
60
60
  },
@@ -85,7 +85,7 @@ export default function longPress(server: FastMCP): void {
85
85
  actions: [
86
86
  { type: 'pointerMove', duration: 0, x, y },
87
87
  { type: 'pointerDown', button: 0 },
88
- { type: 'pause', duration: duration },
88
+ { type: 'pause', duration },
89
89
  { type: 'pointerUp', button: 0 },
90
90
  ],
91
91
  },
@@ -34,7 +34,7 @@ export default function bootSimulator(server: any): void {
34
34
  const simulators = await iosManager.listSimulators();
35
35
 
36
36
  // Find the simulator with the given UDID
37
- const simulator = simulators.find(sim => sim.udid === udid);
37
+ const simulator = simulators.find((sim) => sim.udid === udid);
38
38
 
39
39
  if (!simulator) {
40
40
  throw new Error(
@@ -28,7 +28,7 @@ async function getLatestWDAVersion(): Promise<string> {
28
28
 
29
29
  const entries = await readdir(wdaCacheDir);
30
30
  const versions = await Promise.all(
31
- entries.map(async dir => {
31
+ entries.map(async (dir) => {
32
32
  const dirPath = path.join(wdaCacheDir, dir);
33
33
  const stats = await stat(dirPath);
34
34
  return stats.isDirectory() ? dir : null;
@@ -37,10 +37,10 @@ async function getLatestWDAVersion(): Promise<string> {
37
37
 
38
38
  const filteredVersions = versions
39
39
  .filter((v): v is string => v !== null)
40
- .sort((a, b) => {
40
+ .sort((a, b) =>
41
41
  // Simple version comparison - you might want to use semver for more complex versions
42
- return b.localeCompare(a, undefined, { numeric: true });
43
- });
42
+ b.localeCompare(a, undefined, { numeric: true })
43
+ );
44
44
 
45
45
  if (filteredVersions.length === 0) {
46
46
  throw new Error(
@@ -6,8 +6,7 @@ import { exec } from 'child_process';
6
6
  import { promisify } from 'util';
7
7
  import path from 'path';
8
8
  import { access, mkdir, unlink } from 'fs/promises';
9
- import { constants } from 'fs';
10
- import { createWriteStream } from 'fs';
9
+ import { constants, createWriteStream } from 'fs';
11
10
  import { pipeline } from 'stream/promises';
12
11
  import os from 'os';
13
12
  import axios from 'axios';
@@ -196,7 +195,7 @@ export default function setupWDA(server: any): void {
196
195
  text: `${JSON.stringify(
197
196
  {
198
197
  version: wdaVersion,
199
- platform: platform,
198
+ platform,
200
199
  architecture: archStr,
201
200
  wdaAppPath: appPath,
202
201
  wdaCachePath: `~/.cache/appium-mcp/wda/${wdaVersion}`,
@@ -24,7 +24,7 @@ const getValue = (xpath: string, expression: string): string => {
24
24
  // Extracts the value from an XPath expression.
25
25
  let start = xpath.indexOf(expression) + expression.length;
26
26
  start = xpath.indexOf("'", start) + 1;
27
- let end = xpath.indexOf("'", start);
27
+ const end = xpath.indexOf("'", start);
28
28
  return xpath.substring(start, end);
29
29
  };
30
30
 
@@ -33,26 +33,30 @@ const transformXPath = (
33
33
  ): { strategy: string; selector: string } => {
34
34
  // normalize xpath expression by replacing " by '
35
35
  xpath = xpath.replace(/"/g, "'");
36
- if (xpath.includes('@text='))
36
+ if (xpath.includes('@text=')) {
37
37
  return { strategy: 'text', selector: getValue(xpath, '@text=') };
38
+ }
38
39
 
39
- if (xpath.includes('@content-desc='))
40
+ if (xpath.includes('@content-desc=')) {
40
41
  return {
41
42
  strategy: 'description',
42
43
  selector: getValue(xpath, '@content-desc='),
43
44
  };
45
+ }
44
46
 
45
- if (xpath.includes('contains(@text,'))
47
+ if (xpath.includes('contains(@text,')) {
46
48
  return {
47
49
  strategy: 'textContains',
48
50
  selector: getValue(xpath, 'contains(@text,'),
49
51
  };
52
+ }
50
53
 
51
- if (xpath.includes('contains(@content-desc,'))
54
+ if (xpath.includes('contains(@content-desc,')) {
52
55
  return {
53
56
  strategy: 'descriptionContains',
54
57
  selector: getValue(xpath, 'contains(@content-desc,'),
55
58
  };
59
+ }
56
60
 
57
61
  throw new Error(
58
62
  `Unsupported XPath expression: ${xpath}. Supported expressions are: @text, @content-desc, contains(@text, ...), contains(@content-desc, ...)`
@@ -63,9 +67,15 @@ const transformLocator = (
63
67
  strategy: string,
64
68
  selector: string
65
69
  ): { strategy: string; selector: string } => {
66
- if (strategy === 'id') return { strategy: 'resourceId', selector };
67
- if (strategy === 'xpath') return transformXPath(selector);
68
- if (strategy === 'class name') return { strategy: 'className', selector };
70
+ if (strategy === 'id') {
71
+ return { strategy: 'resourceId', selector };
72
+ }
73
+ if (strategy === 'xpath') {
74
+ return transformXPath(selector);
75
+ }
76
+ if (strategy === 'class name') {
77
+ return { strategy: 'className', selector };
78
+ }
69
79
 
70
80
  return { strategy, selector };
71
81
  };
@@ -103,7 +113,7 @@ async function performiOSScroll(driver: any, direction: string): Promise<void> {
103
113
  const { width, height } = await driver.getWindowRect();
104
114
 
105
115
  await driver.execute('mobile: scroll', {
106
- direction: direction,
116
+ direction,
107
117
  startX: width / 2,
108
118
  startY: direction === 'up' ? height * 0.3 : height * 0.7,
109
119
  endX: width / 2,
@@ -72,18 +72,18 @@ export default function scroll(server: any): void {
72
72
  ? await (driver as Client).executeScript('mobile: scroll', [
73
73
  {
74
74
  direction: args.direction,
75
- startX: startX,
76
- startY: startY,
75
+ startX,
76
+ startY,
77
77
  endX: startX,
78
- endY: endY,
78
+ endY,
79
79
  },
80
80
  ])
81
81
  : await (driver as any).execute('mobile: scroll', {
82
82
  direction: args.direction,
83
- startX: startX,
84
- startY: startY,
83
+ startX,
84
+ startY,
85
85
  endX: startX,
86
- endY: endY,
86
+ endY,
87
87
  });
88
88
  } else {
89
89
  throw new Error(
@@ -67,7 +67,7 @@ async function performAndroidSwipe(
67
67
  { type: 'pointerMove', duration: 0, x: startX, y: startY },
68
68
  { type: 'pointerDown', button: 0 },
69
69
  { type: 'pause', duration: 250 },
70
- { type: 'pointerMove', duration: duration, x: endX, y: endY },
70
+ { type: 'pointerMove', duration, x: endX, y: endY },
71
71
  { type: 'pointerUp', button: 0 },
72
72
  ],
73
73
  },
@@ -102,7 +102,7 @@ async function performiOSSwipe(
102
102
  { type: 'pointerMove', duration: 0, x: startX, y: startY },
103
103
  { type: 'pointerDown', button: 0 },
104
104
  { type: 'pause', duration: 200 },
105
- { type: 'pointerMove', duration: duration, x: endX, y: endY },
105
+ { type: 'pointerMove', duration, x: endX, y: endY },
106
106
  { type: 'pause', duration: 50 },
107
107
  { type: 'pointerUp', button: 0 },
108
108
  ],