@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,384 @@
1
+ import llmConfig, { PromptTemplates } from '../config/llm.config';
2
+ import { UIContext, UIElement } from '../models/testSpec';
3
+
4
+ export class PromptBuilder {
5
+ private maxUIElements: number;
6
+ private maxContextTokens: number;
7
+ private includeAppContext: boolean;
8
+ private includeUIContext: boolean;
9
+
10
+ constructor() {
11
+ this.maxUIElements = llmConfig.prompt.maxUIElements;
12
+ this.maxContextTokens = llmConfig.prompt.maxContextTokens;
13
+ this.includeAppContext = llmConfig.prompt.includeAppContext;
14
+ this.includeUIContext = llmConfig.prompt.includeUIContext;
15
+ }
16
+
17
+ /**
18
+ * Build the main action planning prompt
19
+ */
20
+ buildActionPlanningPrompt(
21
+ stepText: string,
22
+ uiContext: UIContext,
23
+ appKnowledge?: string,
24
+ platform?: 'android' | 'ios'
25
+ ): string {
26
+ const sections: string[] = [];
27
+
28
+ // System context
29
+ sections.push(PromptTemplates.actionPlanning);
30
+ sections.push('');
31
+
32
+ // App-specific knowledge
33
+ if (this.includeAppContext && appKnowledge) {
34
+ sections.push('**App-Specific Knowledge:**');
35
+ sections.push(this.truncateText(appKnowledge, 1000));
36
+ sections.push('');
37
+ }
38
+
39
+ // Platform information
40
+ if (platform) {
41
+ sections.push(`**Platform:** ${platform.toUpperCase()}`);
42
+ sections.push('');
43
+ }
44
+
45
+ // Current UI state
46
+ if (this.includeUIContext) {
47
+ sections.push('**Current UI State:**');
48
+ sections.push(this.formatUIContext(uiContext));
49
+ sections.push('');
50
+ }
51
+
52
+ // Test step to execute
53
+ sections.push('**Test Step to Execute:**');
54
+ sections.push(`"${stepText}"`);
55
+ sections.push('');
56
+
57
+ // Output format instructions
58
+ sections.push(this.getOutputFormatInstructions());
59
+
60
+ return sections.join('\n');
61
+ }
62
+
63
+ /**
64
+ * Build prompt for context analysis
65
+ */
66
+ buildContextAnalysisPrompt(
67
+ uiContext: UIContext,
68
+ focusArea?: string
69
+ ): string {
70
+ const sections: string[] = [];
71
+
72
+ sections.push(PromptTemplates.contextAnalysis);
73
+ sections.push('');
74
+
75
+ if (focusArea) {
76
+ sections.push(`**Focus Area:** ${focusArea}`);
77
+ sections.push('');
78
+ }
79
+
80
+ sections.push('**Current UI State:**');
81
+ sections.push(this.formatUIContext(uiContext));
82
+ sections.push('');
83
+
84
+ sections.push('Identify and list the most relevant interactive elements with their best selectors.');
85
+
86
+ return sections.join('\n');
87
+ }
88
+
89
+ /**
90
+ * Build prompt for error recovery
91
+ */
92
+ buildErrorRecoveryPrompt(
93
+ stepText: string,
94
+ previousError: string,
95
+ uiContext: UIContext,
96
+ attemptNumber: number
97
+ ): string {
98
+ const sections: string[] = [];
99
+
100
+ sections.push(PromptTemplates.errorRecovery);
101
+ sections.push('');
102
+
103
+ sections.push(`**Original Test Step:** "${stepText}"`);
104
+ sections.push(`**Attempt Number:** ${attemptNumber}`);
105
+ sections.push(`**Previous Error:** ${previousError}`);
106
+ sections.push('');
107
+
108
+ sections.push('**Current UI State:**');
109
+ sections.push(this.formatUIContext(uiContext));
110
+ sections.push('');
111
+
112
+ sections.push('**Instructions:**');
113
+ sections.push('1. Analyze why the previous attempt failed');
114
+ sections.push('2. Check if the UI state has changed');
115
+ sections.push('3. Suggest alternative actions or selectors');
116
+ sections.push('4. If the step is impossible, return an empty actions array with reasoning');
117
+ sections.push('');
118
+
119
+ sections.push(this.getOutputFormatInstructions());
120
+
121
+ return sections.join('\n');
122
+ }
123
+
124
+ /**
125
+ * Build prompt for assertion generation
126
+ */
127
+ buildAssertionPrompt(
128
+ expectedOutcome: string,
129
+ uiContext: UIContext
130
+ ): string {
131
+ const sections: string[] = [];
132
+
133
+ sections.push(PromptTemplates.assertionGeneration);
134
+ sections.push('');
135
+
136
+ sections.push(`**Expected Outcome:** "${expectedOutcome}"`);
137
+ sections.push('');
138
+
139
+ sections.push('**Current UI State:**');
140
+ sections.push(this.formatUIContext(uiContext));
141
+ sections.push('');
142
+
143
+ sections.push('Generate assertion actions to verify the expected outcome.');
144
+ sections.push('');
145
+
146
+ sections.push(this.getOutputFormatInstructions());
147
+
148
+ return sections.join('\n');
149
+ }
150
+
151
+ /**
152
+ * Format UI context for inclusion in prompts
153
+ */
154
+ private formatUIContext(uiContext: UIContext): string {
155
+ const lines: string[] = [];
156
+
157
+ // Current screen/activity
158
+ if (uiContext.currentActivity) {
159
+ lines.push(`Current Screen: ${uiContext.currentActivity}`);
160
+ lines.push('');
161
+ }
162
+
163
+ // Visible elements
164
+ const elements = uiContext.visibleElements || [];
165
+
166
+ if (elements.length === 0) {
167
+ lines.push('No visible elements detected.');
168
+ return lines.join('\n');
169
+ }
170
+
171
+ lines.push(`Visible Elements (showing ${Math.min(elements.length, this.maxUIElements)} of ${elements.length}):`);
172
+ lines.push('');
173
+
174
+ // Filter and sort elements by relevance
175
+ const relevantElements = this.filterRelevantElements(elements);
176
+ const topElements = relevantElements.slice(0, this.maxUIElements);
177
+
178
+ topElements.forEach((element, index) => {
179
+ lines.push(this.formatElement(element, index + 1));
180
+ });
181
+
182
+ return lines.join('\n');
183
+ }
184
+
185
+ /**
186
+ * Format a single UI element
187
+ */
188
+ private formatElement(element: UIElement, index: number): string {
189
+ const parts: string[] = [`${index}.`];
190
+
191
+ // Element type
192
+ parts.push(`[${element.type}]`);
193
+
194
+ // Text content
195
+ if (element.text && element.text.trim().length > 0) {
196
+ parts.push(`text="${this.truncateText(element.text, 50)}"`);
197
+ }
198
+
199
+ // Resource ID (Android) or Name (iOS)
200
+ if (element.resourceId) {
201
+ const shortId = element.resourceId.split('/').pop() || element.resourceId;
202
+ parts.push(`id="${shortId}"`);
203
+ }
204
+
205
+ // Content description / accessibility label
206
+ if (element.contentDesc) {
207
+ parts.push(`desc="${this.truncateText(element.contentDesc, 30)}"`);
208
+ }
209
+
210
+ // Attributes
211
+ const attrs: string[] = [];
212
+ if (element.clickable) attrs.push('clickable');
213
+ if (element.enabled) attrs.push('enabled');
214
+ if (!element.enabled) attrs.push('disabled');
215
+
216
+ if (attrs.length > 0) {
217
+ parts.push(`[${attrs.join(', ')}]`);
218
+ }
219
+
220
+ // Bounds (if useful)
221
+ if (element.bounds) {
222
+ parts.push(`bounds=${element.bounds}`);
223
+ }
224
+
225
+ return parts.join(' ');
226
+ }
227
+
228
+ /**
229
+ * Filter elements to show most relevant ones
230
+ */
231
+ private filterRelevantElements(elements: UIElement[]): UIElement[] {
232
+ // Prioritize elements that are:
233
+ // 1. Interactive (clickable, editable)
234
+ // 2. Have meaningful text or labels
235
+ // 3. Are common UI patterns (buttons, inputs, etc.)
236
+
237
+ const scored = elements.map(element => ({
238
+ element,
239
+ score: this.calculateElementRelevance(element),
240
+ }));
241
+
242
+ // Sort by score (descending)
243
+ scored.sort((a, b) => b.score - a.score);
244
+
245
+ return scored.map(s => s.element);
246
+ }
247
+
248
+ /**
249
+ * Calculate relevance score for an element
250
+ */
251
+ private calculateElementRelevance(element: UIElement): number {
252
+ let score = 0;
253
+
254
+ // Interactive elements get high priority
255
+ if (element.clickable) score += 10;
256
+
257
+ // Elements with text are usually important
258
+ if (element.text && element.text.trim().length > 0) {
259
+ score += 8;
260
+ }
261
+
262
+ // Elements with IDs are identifiable
263
+ if (element.resourceId) score += 5;
264
+
265
+ // Elements with accessibility labels
266
+ if (element.contentDesc) score += 5;
267
+
268
+ // Common interactive types
269
+ const interactiveTypes = [
270
+ 'Button', 'EditText', 'TextView', 'ImageButton',
271
+ 'CheckBox', 'RadioButton', 'Switch', 'Spinner',
272
+ 'UIButton', 'UITextField', 'UITextView', 'UILabel',
273
+ ];
274
+
275
+ if (interactiveTypes.some(type => element.type.includes(type))) {
276
+ score += 7;
277
+ }
278
+
279
+ // Enabled elements
280
+ if (element.enabled) score += 3;
281
+
282
+ // Penalize common container types (unless they have text)
283
+ const containerTypes = ['ViewGroup', 'LinearLayout', 'FrameLayout', 'View', 'UIView'];
284
+ if (containerTypes.some(type => element.type.includes(type)) && !element.text) {
285
+ score -= 5;
286
+ }
287
+
288
+ return Math.max(0, score);
289
+ }
290
+
291
+ /**
292
+ * Get output format instructions
293
+ */
294
+ private getOutputFormatInstructions(): string {
295
+ return `**Output Format (JSON only, no markdown):**
296
+ {
297
+ "actions": [
298
+ {
299
+ "type": "tap|type|scroll|swipe|wait|assert|screenshot",
300
+ "selector": {
301
+ "strategy": "text|id|accessibility-id|xpath|class",
302
+ "value": "selector_value",
303
+ "index": 0
304
+ },
305
+ "value": "text_to_type (only for type action)",
306
+ "direction": "up|down|left|right (only for scroll/swipe)",
307
+ "duration": 1000,
308
+ "assertionType": "exists|visible|text|enabled (only for assert)",
309
+ "expectedValue": "expected_value (only for assert)"
310
+ }
311
+ ],
312
+ "reasoning": "Brief explanation of the action sequence and why these selectors were chosen"
313
+ }
314
+
315
+ **Critical Rules:**
316
+ 1. Return ONLY valid JSON, no markdown code blocks
317
+ 2. Always prefer text-based selectors over XPath
318
+ 3. Use accessibility-id or resource-id when available
319
+ 4. Include reasoning for your selector choices
320
+ 5. If no action is possible, return empty actions array with explanation
321
+ 6. Keep action sequences minimal (1-3 actions preferred)
322
+ 7. Add wait actions if UI transitions are expected
323
+
324
+ Generate the JSON action plan now:`;
325
+ }
326
+
327
+ /**
328
+ * Truncate text to max length
329
+ */
330
+ private truncateText(text: string, maxLength: number): string {
331
+ if (text.length <= maxLength) {
332
+ return text;
333
+ }
334
+ return text.substring(0, maxLength - 3) + '...';
335
+ }
336
+
337
+ /**
338
+ * Estimate token count (rough approximation)
339
+ */
340
+ private estimateTokens(text: string): number {
341
+ // Rough estimate: 1 token ≈ 4 characters
342
+ return Math.ceil(text.length / 4);
343
+ }
344
+
345
+ /**
346
+ * Build a few-shot learning prompt with examples
347
+ */
348
+ buildFewShotPrompt(
349
+ stepText: string,
350
+ uiContext: UIContext,
351
+ examples: Array<{ step: string; actions: any; context?: string }>
352
+ ): string {
353
+ const sections: string[] = [];
354
+
355
+ sections.push(PromptTemplates.actionPlanning);
356
+ sections.push('');
357
+
358
+ sections.push('**Example Scenarios:**');
359
+ sections.push('');
360
+
361
+ examples.forEach((example, index) => {
362
+ sections.push(`Example ${index + 1}:`);
363
+ sections.push(`Step: "${example.step}"`);
364
+ if (example.context) {
365
+ sections.push(`Context: ${example.context}`);
366
+ }
367
+ sections.push(`Actions: ${JSON.stringify(example.actions, null, 2)}`);
368
+ sections.push('');
369
+ });
370
+
371
+ sections.push('**Now, solve this:**');
372
+ sections.push('');
373
+ sections.push('**Current UI State:**');
374
+ sections.push(this.formatUIContext(uiContext));
375
+ sections.push('');
376
+ sections.push(`**Test Step:** "${stepText}"`);
377
+ sections.push('');
378
+ sections.push(this.getOutputFormatInstructions());
379
+
380
+ return sections.join('\n');
381
+ }
382
+ }
383
+
384
+ export default PromptBuilder;
@@ -0,0 +1,31 @@
1
+ name: Technician App Login Flow
2
+ description: Test the complete login flow with OTP verification
3
+ platform: Android
4
+
5
+ setup:
6
+ - step: Launch the DmgPro app
7
+ timeout: 10000
8
+
9
+ steps:
10
+ - step: Click on 'Allow' if notifications dialog displayed
11
+ expectedOutcome: Get Started Screen should appear
12
+ timeout: 5000
13
+
14
+ - step: Click on 'Get Started' button
15
+ expectedOutcome: Login screen should appear
16
+ timeout: 5000
17
+
18
+ - step: Input the mobile number "112233445" into the Phone Number input field
19
+ expectedOutcome: OTP screen should appear
20
+ timeout: 5000
21
+
22
+ - step: Input OTP "1111"
23
+ expectedOutcome: Home screen should load
24
+ timeout: 5000
25
+
26
+ - step: Enable location access
27
+ expectedOutcome: Location permission granted
28
+ timeout: 5000
29
+
30
+ teardown:
31
+ - step: Close the app
package/tsconfig.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": [
6
+ "ES2020"
7
+ ],
8
+ "outDir": "./dist",
9
+ "rootDir": "./src",
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "resolveJsonModule": true,
15
+ "moduleResolution": "node",
16
+ "declaration": true,
17
+ "declarationMap": true,
18
+ "sourceMap": true,
19
+ "types": [
20
+ "node"
21
+ ]
22
+ },
23
+ "include": [
24
+ "src/**/*"
25
+ ],
26
+ "exclude": [
27
+ "node_modules",
28
+ "dist",
29
+ "tests"
30
+ ]
31
+ }