@mseep/ai-tech-app-agent 1.0.0

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 (67) hide show
  1. package/.env +24 -0
  2. package/.env.example +24 -0
  3. package/Jenkinsfile +210 -0
  4. package/MCP-SERVER-GUIDE.md +405 -0
  5. package/README.MD +450 -0
  6. package/dist/config/app.config.d.ts +65 -0
  7. package/dist/config/app.config.d.ts.map +1 -0
  8. package/dist/config/app.config.js +94 -0
  9. package/dist/config/app.config.js.map +1 -0
  10. package/dist/config/llm.config.d.ts +63 -0
  11. package/dist/config/llm.config.d.ts.map +1 -0
  12. package/dist/config/llm.config.js +158 -0
  13. package/dist/config/llm.config.js.map +1 -0
  14. package/dist/config/mcp.config.d.ts +175 -0
  15. package/dist/config/mcp.config.d.ts.map +1 -0
  16. package/dist/config/mcp.config.js +215 -0
  17. package/dist/config/mcp.config.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +175 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/llm/llamaClient.d.ts +14 -0
  23. package/dist/llm/llamaClient.d.ts.map +1 -0
  24. package/dist/llm/llamaClient.js +136 -0
  25. package/dist/llm/llamaClient.js.map +1 -0
  26. package/dist/mcp/mcpClient.d.ts +132 -0
  27. package/dist/mcp/mcpClient.d.ts.map +1 -0
  28. package/dist/mcp/mcpClient.js +784 -0
  29. package/dist/mcp/mcpClient.js.map +1 -0
  30. package/dist/models/testSpec.d.ts +78 -0
  31. package/dist/models/testSpec.d.ts.map +1 -0
  32. package/dist/models/testSpec.js +3 -0
  33. package/dist/models/testSpec.js.map +1 -0
  34. package/dist/orchestrator/aiTestRunner.d.ts +18 -0
  35. package/dist/orchestrator/aiTestRunner.d.ts.map +1 -0
  36. package/dist/orchestrator/aiTestRunner.js +247 -0
  37. package/dist/orchestrator/aiTestRunner.js.map +1 -0
  38. package/dist/utils/logger.d.ts +4 -0
  39. package/dist/utils/logger.d.ts.map +1 -0
  40. package/dist/utils/logger.js +49 -0
  41. package/dist/utils/logger.js.map +1 -0
  42. package/dist/utils/promptBuilder.d.ts +62 -0
  43. package/dist/utils/promptBuilder.d.ts.map +1 -0
  44. package/dist/utils/promptBuilder.js +333 -0
  45. package/dist/utils/promptBuilder.js.map +1 -0
  46. package/knowledge/app-knowledge.txt +100 -0
  47. package/logs/combined.log +486 -0
  48. package/logs/error.log +50 -0
  49. package/package.json +62 -0
  50. package/reports/screenshots/screenshot_1764535110518.png +0 -0
  51. package/reports/test-report.json +106 -0
  52. package/scripts/check-mcp-server.sh +100 -0
  53. package/scripts/extract-pom-knowledge.js +222 -0
  54. package/scripts/pre-test-setup.js +262 -0
  55. package/scripts/start-mcp-server.sh +76 -0
  56. package/src/config/app.config.ts +175 -0
  57. package/src/config/llm.config.ts +220 -0
  58. package/src/config/mcp.config.ts +291 -0
  59. package/src/index.ts +161 -0
  60. package/src/llm/llamaClient.ts +159 -0
  61. package/src/mcp/mcpClient.ts +878 -0
  62. package/src/models/testSpec.ts +85 -0
  63. package/src/orchestrator/aiTestRunner.ts +286 -0
  64. package/src/utils/logger.ts +59 -0
  65. package/src/utils/promptBuilder.ts +384 -0
  66. package/tests/nlp-specs/login-flow.yaml +31 -0
  67. package/tsconfig.json +31 -0
@@ -0,0 +1,220 @@
1
+ import dotenv from 'dotenv';
2
+
3
+ dotenv.config();
4
+
5
+ export interface LLMConfig {
6
+ provider: 'ollama' | 'openai' | 'anthropic' | 'custom';
7
+
8
+ // Connection settings
9
+ connection: {
10
+ baseUrl: string;
11
+ timeout: number;
12
+ retryAttempts: number;
13
+ retryDelay: number;
14
+ };
15
+
16
+ // Model settings
17
+ model: {
18
+ name: string;
19
+ temperature: number;
20
+ maxTokens: number;
21
+ topP: number;
22
+ topK: number;
23
+ frequencyPenalty: number;
24
+ presencePenalty: number;
25
+ };
26
+
27
+ // Prompt configuration
28
+ prompt: {
29
+ systemMessage: string;
30
+ includeAppContext: boolean;
31
+ includeUIContext: boolean;
32
+ maxUIElements: number;
33
+ maxContextTokens: number;
34
+ includeReasoningInResponse: boolean;
35
+ strictJsonMode: boolean;
36
+ };
37
+
38
+ // Performance
39
+ performance: {
40
+ cachingEnabled: boolean;
41
+ cacheTTL: number;
42
+ batchRequests: boolean;
43
+ maxBatchSize: number;
44
+ streamResponse: boolean;
45
+ };
46
+
47
+ // Fallback
48
+ fallback: {
49
+ enabled: boolean;
50
+ fallbackModel?: string;
51
+ maxFallbackAttempts: number;
52
+ };
53
+ }
54
+
55
+ const llmConfig: LLMConfig = {
56
+ provider: (process.env.LLM_PROVIDER as 'ollama' | 'openai' | 'anthropic' | 'custom') || 'ollama',
57
+
58
+ connection: {
59
+ baseUrl: process.env.OLLAMA_BASE_URL || process.env.LLM_BASE_URL || 'http://localhost:11434',
60
+ timeout: parseInt(process.env.LLM_TIMEOUT || '60000'),
61
+ retryAttempts: parseInt(process.env.LLM_RETRY_ATTEMPTS || '3'),
62
+ retryDelay: parseInt(process.env.LLM_RETRY_DELAY || '2000'),
63
+ },
64
+
65
+ model: {
66
+ name: process.env.LLM_MODEL || 'llama3.2:3b',
67
+ temperature: parseFloat(process.env.LLM_TEMPERATURE || '0.1'),
68
+ maxTokens: parseInt(process.env.LLM_MAX_TOKENS || '2000'),
69
+ topP: parseFloat(process.env.LLM_TOP_P || '0.9'),
70
+ topK: parseInt(process.env.LLM_TOP_K || '40'),
71
+ frequencyPenalty: parseFloat(process.env.LLM_FREQUENCY_PENALTY || '0.0'),
72
+ presencePenalty: parseFloat(process.env.LLM_PRESENCE_PENALTY || '0.0'),
73
+ },
74
+
75
+ prompt: {
76
+ systemMessage: process.env.LLM_SYSTEM_MESSAGE ||
77
+ 'You are an expert mobile test automation agent specialized in Appium automation.',
78
+ includeAppContext: process.env.LLM_INCLUDE_APP_CONTEXT !== 'false',
79
+ includeUIContext: process.env.LLM_INCLUDE_UI_CONTEXT !== 'false',
80
+ maxUIElements: parseInt(process.env.LLM_MAX_UI_ELEMENTS || '30'),
81
+ maxContextTokens: parseInt(process.env.LLM_MAX_CONTEXT_TOKENS || '4000'),
82
+ includeReasoningInResponse: process.env.LLM_INCLUDE_REASONING !== 'false',
83
+ strictJsonMode: process.env.LLM_STRICT_JSON_MODE !== 'false',
84
+ },
85
+
86
+ performance: {
87
+ cachingEnabled: process.env.LLM_CACHING_ENABLED === 'true',
88
+ cacheTTL: parseInt(process.env.LLM_CACHE_TTL || '3600'),
89
+ batchRequests: process.env.LLM_BATCH_REQUESTS === 'true',
90
+ maxBatchSize: parseInt(process.env.LLM_MAX_BATCH_SIZE || '5'),
91
+ streamResponse: process.env.LLM_STREAM_RESPONSE === 'true',
92
+ },
93
+
94
+ fallback: {
95
+ enabled: process.env.LLM_FALLBACK_ENABLED === 'true',
96
+ fallbackModel: process.env.LLM_FALLBACK_MODEL,
97
+ maxFallbackAttempts: parseInt(process.env.LLM_FALLBACK_ATTEMPTS || '1'),
98
+ },
99
+ };
100
+
101
+ // Predefined prompt templates
102
+ export const PromptTemplates = {
103
+ actionPlanning: `You are an expert mobile test automation agent. Your task is to generate precise Appium actions to accomplish a test step.
104
+
105
+ **Your Capabilities:**
106
+ - Analyze mobile app UI hierarchies (XML format)
107
+ - Identify the best element selectors (text, id, accessibility-id, xpath, class)
108
+ - Generate reliable action sequences (tap, type, scroll, swipe, wait, assert)
109
+ - Handle both Android and iOS platforms
110
+ - Adapt to different screen sizes and device configurations
111
+
112
+ **Priority for Element Selection:**
113
+ 1. Text content (exact or partial match)
114
+ 2. Accessibility ID or Content Description
115
+ 3. Resource ID (Android) or Name (iOS)
116
+ 4. Class name with additional attributes
117
+ 5. XPath (only when necessary)
118
+ 6. Coordinates (last resort)
119
+
120
+ **Action Types Available:**
121
+ - tap: Click/tap on an element
122
+ - type: Enter text into an input field
123
+ - scroll: Scroll in a direction (up, down, left, right)
124
+ - swipe: Swipe gesture in a direction
125
+ - wait: Wait for a duration or element
126
+ - assert: Verify element state (exists, visible, text, enabled)
127
+ - screenshot: Capture screen state`,
128
+
129
+ contextAnalysis: `Analyze the current UI context and identify key interactive elements that are relevant for accomplishing the given test step.
130
+
131
+ **Focus on:**
132
+ - Interactive elements (buttons, inputs, links)
133
+ - Navigation elements (tabs, back buttons)
134
+ - Text labels and headers for context
135
+ - Error messages or alerts
136
+ - Permission dialogs
137
+ - Loading indicators`,
138
+
139
+ errorRecovery: `The previous action attempt failed. Analyze the current UI state and suggest alternative approaches.
140
+
141
+ **Consider:**
142
+ - Is the target element visible and enabled?
143
+ - Are there any blocking dialogs or overlays?
144
+ - Is the app in an unexpected state?
145
+ - Are there alternative selectors for the same element?
146
+ - Should we wait for animations or transitions to complete?`,
147
+
148
+ assertionGeneration: `Generate appropriate assertions to verify the expected outcome of the test step.
149
+
150
+ **Common Assertions:**
151
+ - Element exists on screen
152
+ - Element is visible and enabled
153
+ - Text content matches expected value
154
+ - Specific screen or activity is displayed
155
+ - No error messages are present`,
156
+ };
157
+
158
+ // Helper function to get appropriate template
159
+ export function getPromptTemplate(type: keyof typeof PromptTemplates): string {
160
+ return PromptTemplates[type];
161
+ }
162
+
163
+ // Validation
164
+ export function validateLLMConfig(): void {
165
+ const errors: string[] = [];
166
+
167
+ // Check base URL
168
+ if (!llmConfig.connection.baseUrl) {
169
+ errors.push('LLM_BASE_URL or OLLAMA_BASE_URL is required');
170
+ }
171
+
172
+ // Validate model name
173
+ if (!llmConfig.model.name) {
174
+ errors.push('LLM_MODEL is required');
175
+ }
176
+
177
+ // Validate temperature range
178
+ if (llmConfig.model.temperature < 0 || llmConfig.model.temperature > 2) {
179
+ errors.push('LLM_TEMPERATURE must be between 0 and 2');
180
+ }
181
+
182
+ // Validate max tokens
183
+ if (llmConfig.model.maxTokens < 100 || llmConfig.model.maxTokens > 100000) {
184
+ errors.push('LLM_MAX_TOKENS must be between 100 and 100000');
185
+ }
186
+
187
+ // Validate timeout
188
+ if (llmConfig.connection.timeout < 5000) {
189
+ errors.push('LLM_TIMEOUT must be at least 5000ms');
190
+ }
191
+
192
+ if (errors.length > 0) {
193
+ throw new Error(`LLM configuration validation failed:\n${errors.join('\n')}`);
194
+ }
195
+ }
196
+
197
+ // Get configuration for specific provider
198
+ export function getProviderConfig(provider: string) {
199
+ const configs = {
200
+ ollama: {
201
+ endpoint: '/api/generate',
202
+ healthEndpoint: '/api/tags',
203
+ format: 'ollama',
204
+ },
205
+ openai: {
206
+ endpoint: '/v1/chat/completions',
207
+ healthEndpoint: '/v1/models',
208
+ format: 'openai',
209
+ },
210
+ anthropic: {
211
+ endpoint: '/v1/messages',
212
+ healthEndpoint: '/v1/models',
213
+ format: 'anthropic',
214
+ },
215
+ };
216
+
217
+ return configs[provider as keyof typeof configs] || configs.ollama;
218
+ }
219
+
220
+ export default llmConfig;
@@ -0,0 +1,291 @@
1
+ import dotenv from 'dotenv';
2
+
3
+ dotenv.config();
4
+
5
+ export interface MCPConfig {
6
+ server: {
7
+ type: 'mcp-appium' | 'mobile-mcp' | 'custom';
8
+ host: string;
9
+ port: number;
10
+ protocol: 'http' | 'https' | 'stdio';
11
+ timeout: number;
12
+ };
13
+
14
+ appium: {
15
+ serverUrl: string;
16
+ sessionId?: string;
17
+ capabilities: {
18
+ platformName?: string;
19
+ platformVersion?: string;
20
+ deviceName?: string;
21
+ automationName?: string;
22
+ app?: string;
23
+ appPackage?: string;
24
+ appActivity?: string;
25
+ bundleId?: string;
26
+ };
27
+ };
28
+
29
+ tools: {
30
+ enabledTools: string[];
31
+ customTools?: Record<string, any>;
32
+ };
33
+
34
+ uiContext: {
35
+ capturePageSource: boolean;
36
+ parseXML: boolean;
37
+ includeInvisibleElements: boolean;
38
+ maxElementDepth: number;
39
+ elementFilters: {
40
+ excludeClasses: string[];
41
+ includeOnlyInteractive: boolean;
42
+ minTextLength: number;
43
+ };
44
+ };
45
+
46
+ actions: {
47
+ tapStrategy: 'center' | 'visible-center' | 'coordinates';
48
+ typeStrategy: 'native' | 'adb' | 'auto';
49
+ scrollStrategy: 'uiautomator' | 'gesture' | 'auto';
50
+ swipeStrategy: 'touch-action' | 'gesture' | 'auto';
51
+ waitStrategy: 'implicit' | 'explicit' | 'smart';
52
+ };
53
+
54
+ errorHandling: {
55
+ retryOnStaleElement: boolean;
56
+ retryOnNoSuchElement: boolean;
57
+ retryAttempts: number;
58
+ retryDelay: number;
59
+ captureScreenshotOnError: boolean;
60
+ capturePageSourceOnError: boolean;
61
+ };
62
+
63
+ performance: {
64
+ cacheUIContext: boolean;
65
+ cacheDuration: number;
66
+ parallelActions: boolean;
67
+ throttleActions: number;
68
+ };
69
+ }
70
+
71
+ const mcpConfig: MCPConfig = {
72
+ server: {
73
+ type: (process.env.MCP_SERVER_TYPE as 'mcp-appium' | 'mobile-mcp' | 'custom') || 'mcp-appium',
74
+ host: process.env.MCP_SERVER_HOST || 'localhost',
75
+ port: parseInt(process.env.MCP_SERVER_PORT || '3000'),
76
+ protocol: (process.env.MCP_PROTOCOL as 'http' | 'https' | 'stdio') || 'http',
77
+ timeout: parseInt(process.env.MCP_TIMEOUT || '30000'),
78
+ },
79
+
80
+ appium: {
81
+ serverUrl: process.env.APPIUM_SERVER_URL || 'http://localhost:4723',
82
+ sessionId: process.env.APPIUM_SESSION_ID,
83
+ capabilities: {
84
+ platformName: process.env.PLATFORM_NAME,
85
+ platformVersion: process.env.PLATFORM_VERSION,
86
+ deviceName: process.env.DEVICE_NAME,
87
+ automationName: process.env.AUTOMATION_NAME,
88
+ app: process.env.APP_PATH,
89
+ appPackage: process.env.APP_PACKAGE,
90
+ appActivity: process.env.APP_ACTIVITY,
91
+ bundleId: process.env.BUNDLE_ID,
92
+ },
93
+ },
94
+
95
+ tools: {
96
+ enabledTools: (process.env.MCP_ENABLED_TOOLS ||
97
+ 'find_element,tap,type,scroll,swipe,get_page_source,take_screenshot').split(','),
98
+ customTools: {},
99
+ },
100
+
101
+ uiContext: {
102
+ capturePageSource: process.env.MCP_CAPTURE_PAGE_SOURCE !== 'false',
103
+ parseXML: process.env.MCP_PARSE_XML !== 'false',
104
+ includeInvisibleElements: process.env.MCP_INCLUDE_INVISIBLE === 'true',
105
+ maxElementDepth: parseInt(process.env.MCP_MAX_ELEMENT_DEPTH || '10'),
106
+ elementFilters: {
107
+ excludeClasses: (process.env.MCP_EXCLUDE_CLASSES ||
108
+ 'android.view.ViewGroup,android.widget.LinearLayout,UIView').split(','),
109
+ includeOnlyInteractive: process.env.MCP_ONLY_INTERACTIVE === 'true',
110
+ minTextLength: parseInt(process.env.MCP_MIN_TEXT_LENGTH || '1'),
111
+ },
112
+ },
113
+
114
+ actions: {
115
+ tapStrategy: (process.env.MCP_TAP_STRATEGY as 'center' | 'visible-center' | 'coordinates') || 'center',
116
+ typeStrategy: (process.env.MCP_TYPE_STRATEGY as 'native' | 'adb' | 'auto') || 'native',
117
+ scrollStrategy: (process.env.MCP_SCROLL_STRATEGY as 'uiautomator' | 'gesture' | 'auto') || 'auto',
118
+ swipeStrategy: (process.env.MCP_SWIPE_STRATEGY as 'touch-action' | 'gesture' | 'auto') || 'auto',
119
+ waitStrategy: (process.env.MCP_WAIT_STRATEGY as 'implicit' | 'explicit' | 'smart') || 'smart',
120
+ },
121
+
122
+ errorHandling: {
123
+ retryOnStaleElement: process.env.MCP_RETRY_STALE !== 'false',
124
+ retryOnNoSuchElement: process.env.MCP_RETRY_NO_ELEMENT === 'true',
125
+ retryAttempts: parseInt(process.env.MCP_ERROR_RETRY_ATTEMPTS || '2'),
126
+ retryDelay: parseInt(process.env.MCP_ERROR_RETRY_DELAY || '1000'),
127
+ captureScreenshotOnError: process.env.MCP_SCREENSHOT_ON_ERROR !== 'false',
128
+ capturePageSourceOnError: process.env.MCP_PAGE_SOURCE_ON_ERROR === 'true',
129
+ },
130
+
131
+ performance: {
132
+ cacheUIContext: process.env.MCP_CACHE_UI_CONTEXT === 'true',
133
+ cacheDuration: parseInt(process.env.MCP_CACHE_DURATION || '5000'),
134
+ parallelActions: process.env.MCP_PARALLEL_ACTIONS === 'true',
135
+ throttleActions: parseInt(process.env.MCP_THROTTLE_ACTIONS || '0'),
136
+ },
137
+ };
138
+
139
+ // MCP Tool definitions
140
+ export const MCPTools = {
141
+ find_element: {
142
+ name: 'find_element',
143
+ description: 'Find an element on the screen using various selector strategies',
144
+ parameters: {
145
+ strategy: ['id', 'xpath', 'accessibility-id', 'text', 'class'],
146
+ value: 'string',
147
+ multiple: 'boolean',
148
+ },
149
+ },
150
+ tap: {
151
+ name: 'tap',
152
+ description: 'Tap/click on an element',
153
+ parameters: {
154
+ selector: 'object',
155
+ x: 'number (optional)',
156
+ y: 'number (optional)',
157
+ duration: 'number (optional)',
158
+ },
159
+ },
160
+ type: {
161
+ name: 'type',
162
+ description: 'Type text into an element',
163
+ parameters: {
164
+ selector: 'object',
165
+ text: 'string',
166
+ clear: 'boolean (optional)',
167
+ },
168
+ },
169
+ scroll: {
170
+ name: 'scroll',
171
+ description: 'Scroll the screen',
172
+ parameters: {
173
+ direction: ['up', 'down', 'left', 'right'],
174
+ percentage: 'number (optional)',
175
+ element: 'object (optional)',
176
+ },
177
+ },
178
+ swipe: {
179
+ name: 'swipe',
180
+ description: 'Perform swipe gesture',
181
+ parameters: {
182
+ direction: ['up', 'down', 'left', 'right'],
183
+ startX: 'number (optional)',
184
+ startY: 'number (optional)',
185
+ endX: 'number (optional)',
186
+ endY: 'number (optional)',
187
+ duration: 'number (optional)',
188
+ },
189
+ },
190
+ get_page_source: {
191
+ name: 'get_page_source',
192
+ description: 'Get the current page source (XML hierarchy)',
193
+ parameters: {},
194
+ },
195
+ take_screenshot: {
196
+ name: 'take_screenshot',
197
+ description: 'Capture a screenshot',
198
+ parameters: {
199
+ path: 'string (optional)',
200
+ },
201
+ },
202
+ wait: {
203
+ name: 'wait',
204
+ description: 'Wait for element or duration',
205
+ parameters: {
206
+ selector: 'object (optional)',
207
+ condition: ['exists', 'visible', 'enabled', 'clickable'],
208
+ timeout: 'number (optional)',
209
+ duration: 'number (optional)',
210
+ },
211
+ },
212
+ get_current_activity: {
213
+ name: 'get_current_activity',
214
+ description: 'Get current activity (Android) or bundle ID (iOS)',
215
+ parameters: {},
216
+ },
217
+ press_key: {
218
+ name: 'press_key',
219
+ description: 'Press device key',
220
+ parameters: {
221
+ key: ['home', 'back', 'enter', 'menu', 'volume_up', 'volume_down'],
222
+ },
223
+ },
224
+ };
225
+
226
+ // Selector strategies priority
227
+ export const SelectorPriority = [
228
+ 'accessibility-id',
229
+ 'id',
230
+ 'text',
231
+ 'class',
232
+ 'xpath',
233
+ 'coordinates',
234
+ ];
235
+
236
+ // Platform-specific configurations
237
+ export const PlatformConfig = {
238
+ android: {
239
+ automationName: 'UiAutomator2',
240
+ defaultTimeout: 30000,
241
+ keyboardStrategy: 'adb',
242
+ preferredSelectors: ['resource-id', 'content-desc', 'text'],
243
+ },
244
+ ios: {
245
+ automationName: 'XCUITest',
246
+ defaultTimeout: 30000,
247
+ keyboardStrategy: 'native',
248
+ preferredSelectors: ['accessibility-id', 'name', 'label'],
249
+ },
250
+ };
251
+
252
+ // Validation
253
+ export function validateMCPConfig(): void {
254
+ const errors: string[] = [];
255
+
256
+ // Check Appium server URL
257
+ if (!mcpConfig.appium.serverUrl) {
258
+ errors.push('APPIUM_SERVER_URL is required');
259
+ }
260
+
261
+ // Validate server port
262
+ if (mcpConfig.server.port < 1 || mcpConfig.server.port > 65535) {
263
+ errors.push('MCP_SERVER_PORT must be between 1 and 65535');
264
+ }
265
+
266
+ // Validate timeout
267
+ if (mcpConfig.server.timeout < 1000) {
268
+ errors.push('MCP_TIMEOUT must be at least 1000ms');
269
+ }
270
+
271
+ // Validate element depth
272
+ if (mcpConfig.uiContext.maxElementDepth < 1 || mcpConfig.uiContext.maxElementDepth > 20) {
273
+ errors.push('MCP_MAX_ELEMENT_DEPTH must be between 1 and 20');
274
+ }
275
+
276
+ if (errors.length > 0) {
277
+ throw new Error(`MCP configuration validation failed:\n${errors.join('\n')}`);
278
+ }
279
+ }
280
+
281
+ // Helper to get platform-specific config
282
+ export function getPlatformConfig(platform: 'android' | 'ios') {
283
+ return PlatformConfig[platform];
284
+ }
285
+
286
+ // Helper to check if tool is enabled
287
+ export function isToolEnabled(toolName: string): boolean {
288
+ return mcpConfig.tools.enabledTools.includes(toolName);
289
+ }
290
+
291
+ export default mcpConfig;
package/src/index.ts ADDED
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+ import dotenv from 'dotenv';
7
+ import logger from './utils/logger';
8
+ import AITestRunner from './orchestrator/aiTestRunner';
9
+
10
+ dotenv.config();
11
+
12
+ const program = new Command();
13
+
14
+ program
15
+ .name('ai-test-runner')
16
+ .description('AI-powered mobile test automation using MCP and LLM')
17
+ .version('1.0.0');
18
+
19
+ program
20
+ .command('run')
21
+ .description('Run AI-powered test specs')
22
+ .argument('<spec-path>', 'Path to test spec file or directory')
23
+ .option('-s, --session-id <id>', 'Existing Appium session ID')
24
+ .option('-k, --knowledge <path>', 'Path to app knowledge file')
25
+ .option('-o, --output <path>', 'Output path for test report', './reports/test-report.json')
26
+ .action(async (specPath: string, options: any) => {
27
+ try {
28
+ logger.info('Starting AI Test Runner...');
29
+ logger.info(`Spec path: ${specPath}`);
30
+
31
+ const runner = new AITestRunner();
32
+
33
+ // Load app knowledge if provided
34
+ if (options.knowledge) {
35
+ await runner.loadAppKnowledge(options.knowledge);
36
+ }
37
+
38
+ const results = [];
39
+
40
+ // Check if spec path is a file or directory
41
+ const stats = fs.statSync(specPath);
42
+
43
+ if (stats.isDirectory()) {
44
+ // Run all specs in directory
45
+ const files = fs.readdirSync(specPath)
46
+ .filter(f => f.endsWith('.yaml') || f.endsWith('.yml') || f.endsWith('.json'))
47
+ .map(f => path.join(specPath, f));
48
+
49
+ logger.info(`Found ${files.length} test spec(s) in directory`);
50
+
51
+ for (const file of files) {
52
+ const result = await runner.runTestSpec(file, options.sessionId);
53
+ results.push(result);
54
+ }
55
+ } else {
56
+ // Run single spec
57
+ const result = await runner.runTestSpec(specPath, options.sessionId);
58
+ results.push(result);
59
+ }
60
+
61
+ // Generate report
62
+ await runner.generateReport(results, options.output);
63
+
64
+ // Print summary
65
+ console.log('\n' + '='.repeat(60));
66
+ console.log('TEST EXECUTION SUMMARY');
67
+ console.log('='.repeat(60));
68
+
69
+ const passed = results.filter(r => r.status === 'passed').length;
70
+ const failed = results.filter(r => r.status === 'failed').length;
71
+ const total = results.length;
72
+
73
+ console.log(`Total Tests: ${total}`);
74
+ console.log(`Passed: ${passed}`);
75
+ console.log(`Failed: ${failed}`);
76
+ console.log(`Success Rate: ${((passed / total) * 100).toFixed(2)}%`);
77
+ console.log('='.repeat(60) + '\n');
78
+
79
+ // Exit with appropriate code
80
+ process.exit(failed > 0 ? 1 : 0);
81
+
82
+ } catch (error: any) {
83
+ logger.error(`Test execution failed: ${error.message}`);
84
+ console.error(`Error: ${error.message}`);
85
+ process.exit(1);
86
+ }
87
+ });
88
+
89
+ program
90
+ .command('validate')
91
+ .description('Validate test spec file(s)')
92
+ .argument('<spec-path>', 'Path to test spec file or directory')
93
+ .action((specPath: string) => {
94
+ try {
95
+ const stats = fs.statSync(specPath);
96
+ const files = stats.isDirectory()
97
+ ? fs.readdirSync(specPath)
98
+ .filter(f => f.endsWith('.yaml') || f.endsWith('.yml') || f.endsWith('.json'))
99
+ .map(f => path.join(specPath, f))
100
+ : [specPath];
101
+
102
+ let valid = 0;
103
+ let invalid = 0;
104
+
105
+ files.forEach(file => {
106
+ try {
107
+ const content = fs.readFileSync(file, 'utf-8');
108
+ const ext = path.extname(file).toLowerCase();
109
+
110
+ if (ext === '.json') {
111
+ JSON.parse(content);
112
+ } else {
113
+ // For YAML, we'd need to validate structure
114
+ // This is a basic check
115
+ }
116
+
117
+ console.log(`✓ ${file} - Valid`);
118
+ valid++;
119
+ } catch (error: any) {
120
+ console.error(`✗ ${file} - Invalid: ${error.message}`);
121
+ invalid++;
122
+ }
123
+ });
124
+
125
+ console.log(`\nValidation complete: ${valid} valid, ${invalid} invalid`);
126
+ process.exit(invalid > 0 ? 1 : 0);
127
+
128
+ } catch (error: any) {
129
+ console.error(`Validation failed: ${error.message}`);
130
+ process.exit(1);
131
+ }
132
+ });
133
+
134
+ program
135
+ .command('health')
136
+ .description('Check health of LLM and MCP components')
137
+ .action(async () => {
138
+ try {
139
+ const runner = new AITestRunner();
140
+
141
+ console.log('Checking LLM health...');
142
+ const llamaClient = new (await import('./llm/llamaClient')).default();
143
+ const llmHealthy = await llamaClient.healthCheck();
144
+
145
+ if (llmHealthy) {
146
+ console.log('✓ LLM is healthy');
147
+ } else {
148
+ console.error('✗ LLM is not available');
149
+ }
150
+
151
+ // Add MCP health check here if needed
152
+
153
+ process.exit(llmHealthy ? 0 : 1);
154
+
155
+ } catch (error: any) {
156
+ console.error(`Health check failed: ${error.message}`);
157
+ process.exit(1);
158
+ }
159
+ });
160
+
161
+ program.parse(process.argv);