@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.
- package/.env +24 -0
- package/.env.example +24 -0
- package/Jenkinsfile +210 -0
- package/MCP-SERVER-GUIDE.md +405 -0
- package/README.MD +450 -0
- package/dist/config/app.config.d.ts +65 -0
- package/dist/config/app.config.d.ts.map +1 -0
- package/dist/config/app.config.js +94 -0
- package/dist/config/app.config.js.map +1 -0
- package/dist/config/llm.config.d.ts +63 -0
- package/dist/config/llm.config.d.ts.map +1 -0
- package/dist/config/llm.config.js +158 -0
- package/dist/config/llm.config.js.map +1 -0
- package/dist/config/mcp.config.d.ts +175 -0
- package/dist/config/mcp.config.d.ts.map +1 -0
- package/dist/config/mcp.config.js +215 -0
- package/dist/config/mcp.config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +175 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/llamaClient.d.ts +14 -0
- package/dist/llm/llamaClient.d.ts.map +1 -0
- package/dist/llm/llamaClient.js +136 -0
- package/dist/llm/llamaClient.js.map +1 -0
- package/dist/mcp/mcpClient.d.ts +132 -0
- package/dist/mcp/mcpClient.d.ts.map +1 -0
- package/dist/mcp/mcpClient.js +784 -0
- package/dist/mcp/mcpClient.js.map +1 -0
- package/dist/models/testSpec.d.ts +78 -0
- package/dist/models/testSpec.d.ts.map +1 -0
- package/dist/models/testSpec.js +3 -0
- package/dist/models/testSpec.js.map +1 -0
- package/dist/orchestrator/aiTestRunner.d.ts +18 -0
- package/dist/orchestrator/aiTestRunner.d.ts.map +1 -0
- package/dist/orchestrator/aiTestRunner.js +247 -0
- package/dist/orchestrator/aiTestRunner.js.map +1 -0
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +49 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/promptBuilder.d.ts +62 -0
- package/dist/utils/promptBuilder.d.ts.map +1 -0
- package/dist/utils/promptBuilder.js +333 -0
- package/dist/utils/promptBuilder.js.map +1 -0
- package/knowledge/app-knowledge.txt +100 -0
- package/logs/combined.log +486 -0
- package/logs/error.log +50 -0
- package/package.json +62 -0
- package/reports/screenshots/screenshot_1764535110518.png +0 -0
- package/reports/test-report.json +106 -0
- package/scripts/check-mcp-server.sh +100 -0
- package/scripts/extract-pom-knowledge.js +222 -0
- package/scripts/pre-test-setup.js +262 -0
- package/scripts/start-mcp-server.sh +76 -0
- package/src/config/app.config.ts +175 -0
- package/src/config/llm.config.ts +220 -0
- package/src/config/mcp.config.ts +291 -0
- package/src/index.ts +161 -0
- package/src/llm/llamaClient.ts +159 -0
- package/src/mcp/mcpClient.ts +878 -0
- package/src/models/testSpec.ts +85 -0
- package/src/orchestrator/aiTestRunner.ts +286 -0
- package/src/utils/logger.ts +59 -0
- package/src/utils/promptBuilder.ts +384 -0
- package/tests/nlp-specs/login-flow.yaml +31 -0
- package/tsconfig.json +31 -0
package/logs/error.log
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{"level":"error","message":"Failed to initialize MCP server: Cannot connect to Appium server at http://localhost:4723: Error","service":"ai-test-runner","timestamp":"2025-11-30 22:10:29"}
|
|
2
|
+
{"level":"error","message":"Failed to initialize MCP server: Cannot connect to Appium server at http://localhost:4723: Error","service":"ai-test-runner","timestamp":"2025-11-30 22:42:14"}
|
|
3
|
+
{"level":"error","message":"Failed to get UI context: No active Appium session","service":"ai-test-runner","timestamp":"2025-11-30 22:44:49"}
|
|
4
|
+
{"level":"error","message":"Failed to get UI context: No active Appium session","service":"ai-test-runner","timestamp":"2025-11-30 22:44:51"}
|
|
5
|
+
{"level":"error","message":"Failed to get UI context: No active Appium session","service":"ai-test-runner","timestamp":"2025-11-30 22:44:53"}
|
|
6
|
+
{"level":"error","message":"Screenshot failed: Request failed with status code 500","service":"ai-test-runner","timestamp":"2025-11-30 22:44:54"}
|
|
7
|
+
{"level":"error","message":"Failed to initialize MCP server: Failed to create Appium session: Request failed with status code 500","service":"ai-test-runner","timestamp":"2025-11-30 22:46:34"}
|
|
8
|
+
{"level":"error","message":"Session creation failed with status 500","service":"ai-test-runner","timestamp":"2025-11-30 22:48:06"}
|
|
9
|
+
{"level":"error","message":"Session creation failed with status 500","service":"ai-test-runner","timestamp":"2025-11-30 22:49:00"}
|
|
10
|
+
{"level":"error","message":"Test execution failed: Setup failed at step: Launch the DmgPro app","service":"ai-test-runner","timestamp":"2025-11-30 22:55:03"}
|
|
11
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 22:57:34"}
|
|
12
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 22:57:43"}
|
|
13
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 22:57:53"}
|
|
14
|
+
{"level":"error","message":"Test failed at step: Click on 'Allow' if notifications dialog displayed","service":"ai-test-runner","timestamp":"2025-11-30 22:57:57"}
|
|
15
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 22:58:01"}
|
|
16
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 22:58:07"}
|
|
17
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 22:58:12"}
|
|
18
|
+
{"level":"error","message":"Failed to initialize MCP server: Failed to create Appium session: timeout of 30000ms exceeded","service":"ai-test-runner","timestamp":"2025-11-30 23:00:09"}
|
|
19
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 23:02:26"}
|
|
20
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 23:02:33"}
|
|
21
|
+
{"level":"error","message":"Test execution failed: Setup failed at step: Launch the DmgPro app","service":"ai-test-runner","timestamp":"2025-11-30 23:02:35"}
|
|
22
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 23:05:32"}
|
|
23
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 23:05:45"}
|
|
24
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 23:05:58"}
|
|
25
|
+
{"level":"error","message":"Test failed at step: Click on 'Allow' if notifications dialog displayed","service":"ai-test-runner","timestamp":"2025-11-30 23:06:01"}
|
|
26
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 23:06:05"}
|
|
27
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 23:06:14"}
|
|
28
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-11-30 23:06:24"}
|
|
29
|
+
{"level":"error","message":"Failed to initialize MCP server: Failed to create Appium session: timeout of 30000ms exceeded","service":"ai-test-runner","timestamp":"2025-12-01 01:55:39"}
|
|
30
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 01:58:17"}
|
|
31
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 01:58:23"}
|
|
32
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 01:58:28"}
|
|
33
|
+
{"level":"error","message":"Test failed at step: Click on 'Get Started' button","service":"ai-test-runner","timestamp":"2025-12-01 01:58:30"}
|
|
34
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 01:58:38"}
|
|
35
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 01:58:47"}
|
|
36
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 01:58:56"}
|
|
37
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 02:02:33"}
|
|
38
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 02:02:41"}
|
|
39
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 02:02:50"}
|
|
40
|
+
{"level":"error","message":"Test failed at step: Login with mobile number \"112233445\"","service":"ai-test-runner","timestamp":"2025-12-01 02:02:52"}
|
|
41
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 02:03:00"}
|
|
42
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 02:03:05"}
|
|
43
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 02:03:11"}
|
|
44
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 02:07:49"}
|
|
45
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 02:07:57"}
|
|
46
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 02:08:06"}
|
|
47
|
+
{"level":"error","message":"Test failed at step: Input the mobile number \"112233445\" into the Phone Number input field","service":"ai-test-runner","timestamp":"2025-12-01 02:08:08"}
|
|
48
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 02:08:17"}
|
|
49
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 02:08:22"}
|
|
50
|
+
{"level":"error","message":"Tap failed: Element not found","service":"ai-test-runner","timestamp":"2025-12-01 02:08:28"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mseep/ai-tech-app-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-powered mobile test automation using MCP and LLM",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai-test-runner": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"dev": "ts-node src/index.ts",
|
|
13
|
+
"test": "npm run build && node dist/index.js run tests/nlp-specs",
|
|
14
|
+
"test:single": "npm run build && node dist/index.js run tests/nlp-specs/login-flow.yaml",
|
|
15
|
+
"test:knowledge": "npm run build && node dist/index.js run tests/nlp-specs/login-flow.yaml --knowledge knowledge/app-knowledge.txt --output reports/test-report.json",
|
|
16
|
+
"validate": "node dist/index.js validate tests/nlp-specs",
|
|
17
|
+
"health": "node dist/index.js health",
|
|
18
|
+
"check-services": "bash scripts/check-mcp-server.sh",
|
|
19
|
+
"start-mcp": "bash scripts/start-mcp-server.sh",
|
|
20
|
+
"setup": "node scripts/pre-test-setup.js",
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"lint": "eslint src/**/*.ts",
|
|
23
|
+
"prepublish": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mobile",
|
|
27
|
+
"automation",
|
|
28
|
+
"ai",
|
|
29
|
+
"mcp",
|
|
30
|
+
"llm",
|
|
31
|
+
"appium",
|
|
32
|
+
"testing",
|
|
33
|
+
"mseep",
|
|
34
|
+
"mcp-server"
|
|
35
|
+
],
|
|
36
|
+
"author": "Your Name",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"mcp-appium": "^1.0.0",
|
|
40
|
+
"axios": "^1.6.0",
|
|
41
|
+
"js-yaml": "^4.1.0",
|
|
42
|
+
"winston": "^3.11.0",
|
|
43
|
+
"dotenv": "^16.3.1",
|
|
44
|
+
"commander": "^11.1.0",
|
|
45
|
+
"allure-mocha": "^2.14.0",
|
|
46
|
+
"chai": "^4.3.10",
|
|
47
|
+
"fast-xml-parser": "^4.3.2"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^20.10.0",
|
|
51
|
+
"@types/js-yaml": "^4.0.9",
|
|
52
|
+
"typescript": "^5.3.0",
|
|
53
|
+
"ts-node": "^10.9.2",
|
|
54
|
+
"@typescript-eslint/eslint-plugin": "^6.13.0",
|
|
55
|
+
"@typescript-eslint/parser": "^6.13.0",
|
|
56
|
+
"eslint": "^8.54.0"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18.0.0"
|
|
60
|
+
},
|
|
61
|
+
"publisher": "mseep"
|
|
62
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
{
|
|
2
|
+
"timestamp": "2025-11-30T20:38:31.008Z",
|
|
3
|
+
"summary": {
|
|
4
|
+
"total": 1,
|
|
5
|
+
"passed": 0,
|
|
6
|
+
"failed": 1,
|
|
7
|
+
"skipped": 0
|
|
8
|
+
},
|
|
9
|
+
"results": [
|
|
10
|
+
{
|
|
11
|
+
"specName": "Technician App Login Flow",
|
|
12
|
+
"status": "failed",
|
|
13
|
+
"duration": 104020,
|
|
14
|
+
"steps": [
|
|
15
|
+
{
|
|
16
|
+
"step": "Launch the DmgPro app",
|
|
17
|
+
"status": "passed",
|
|
18
|
+
"duration": 13851,
|
|
19
|
+
"actions": [
|
|
20
|
+
{
|
|
21
|
+
"type": "tap",
|
|
22
|
+
"selector": {
|
|
23
|
+
"strategy": "text",
|
|
24
|
+
"value": "Allow DMG Pro to send you notifications?"
|
|
25
|
+
},
|
|
26
|
+
"direction": null,
|
|
27
|
+
"assertionType": "exists",
|
|
28
|
+
"expectedValue": null
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "wait",
|
|
32
|
+
"selector": {
|
|
33
|
+
"strategy": "xpath",
|
|
34
|
+
"value": "//android.widget.Button[@id='com.android.permissioncontroller:id/permission_allow_button']"
|
|
35
|
+
},
|
|
36
|
+
"direction": null,
|
|
37
|
+
"assertionType": "enabled",
|
|
38
|
+
"expectedValue": null
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "tap",
|
|
42
|
+
"selector": {
|
|
43
|
+
"strategy": "text",
|
|
44
|
+
"value": "Allow"
|
|
45
|
+
},
|
|
46
|
+
"direction": null,
|
|
47
|
+
"assertionType": "exists",
|
|
48
|
+
"expectedValue": null
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"step": "Click on 'Allow' if notifications dialog displayed",
|
|
54
|
+
"status": "passed",
|
|
55
|
+
"duration": 9555,
|
|
56
|
+
"actions": [
|
|
57
|
+
{
|
|
58
|
+
"type": "wait",
|
|
59
|
+
"selector": {
|
|
60
|
+
"strategy": "exists",
|
|
61
|
+
"value": "android.widget.Button text='Allow' id='com.android.permissioncontroller:id/permission_allow_button'",
|
|
62
|
+
"index": 0
|
|
63
|
+
},
|
|
64
|
+
"direction": "",
|
|
65
|
+
"assertionType": "exists",
|
|
66
|
+
"expectedValue": ""
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"type": "tap",
|
|
70
|
+
"selector": {
|
|
71
|
+
"strategy": "id",
|
|
72
|
+
"value": "com.android.permissioncontroller:id/permission_allow_button"
|
|
73
|
+
},
|
|
74
|
+
"value": "",
|
|
75
|
+
"direction": "",
|
|
76
|
+
"assertionType": "enabled",
|
|
77
|
+
"expectedValue": ""
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"step": "Click on 'Get Started' button",
|
|
83
|
+
"status": "passed",
|
|
84
|
+
"duration": 5544,
|
|
85
|
+
"actions": [
|
|
86
|
+
{
|
|
87
|
+
"type": "tap",
|
|
88
|
+
"selector": {
|
|
89
|
+
"strategy": "text",
|
|
90
|
+
"value": "Get Started"
|
|
91
|
+
},
|
|
92
|
+
"direction": "up"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"step": "Input the mobile number \"112233445\" into the Phone Number input field",
|
|
98
|
+
"status": "failed",
|
|
99
|
+
"duration": 27788,
|
|
100
|
+
"actions": [],
|
|
101
|
+
"error": "Action failed: tap"
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Color codes for output
|
|
4
|
+
RED='\033[0;31m'
|
|
5
|
+
GREEN='\033[0;32m'
|
|
6
|
+
YELLOW='\033[1;33m'
|
|
7
|
+
NC='\033[0m' # No Color
|
|
8
|
+
|
|
9
|
+
echo "=========================================="
|
|
10
|
+
echo "MCP Server Status Checker"
|
|
11
|
+
echo "=========================================="
|
|
12
|
+
echo ""
|
|
13
|
+
|
|
14
|
+
# Check if Ollama (LLM) is running
|
|
15
|
+
echo "1. Checking Ollama (LLM) Server..."
|
|
16
|
+
if pgrep -x "ollama" > /dev/null; then
|
|
17
|
+
echo -e "${GREEN}✓${NC} Ollama is running"
|
|
18
|
+
|
|
19
|
+
# Try to query Ollama
|
|
20
|
+
if curl -s http://localhost:11434/api/tags > /dev/null 2>&1; then
|
|
21
|
+
echo -e "${GREEN}✓${NC} Ollama API is accessible"
|
|
22
|
+
|
|
23
|
+
# Check if llama3.2:3b is available
|
|
24
|
+
if curl -s http://localhost:11434/api/tags | grep -q "llama3.2:3b"; then
|
|
25
|
+
echo -e "${GREEN}✓${NC} llama3.2:3b model is available"
|
|
26
|
+
else
|
|
27
|
+
echo -e "${YELLOW}⚠${NC} llama3.2:3b model not found"
|
|
28
|
+
echo " Run: ollama pull llama3.2:3b"
|
|
29
|
+
fi
|
|
30
|
+
else
|
|
31
|
+
echo -e "${RED}✗${NC} Ollama API is not accessible"
|
|
32
|
+
fi
|
|
33
|
+
else
|
|
34
|
+
echo -e "${RED}✗${NC} Ollama is not running"
|
|
35
|
+
echo " Start with: ollama serve"
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
echo ""
|
|
39
|
+
|
|
40
|
+
# Check if Appium is running
|
|
41
|
+
echo "2. Checking Appium Server..."
|
|
42
|
+
APPIUM_URL="${APPIUM_SERVER_URL:-http://localhost:4723}"
|
|
43
|
+
|
|
44
|
+
if curl -s "$APPIUM_URL/status" > /dev/null 2>&1; then
|
|
45
|
+
echo -e "${GREEN}✓${NC} Appium server is running at $APPIUM_URL"
|
|
46
|
+
else
|
|
47
|
+
echo -e "${YELLOW}⚠${NC} Appium server is not running locally"
|
|
48
|
+
echo " This is OK if you're using LambdaTest remote grid"
|
|
49
|
+
echo " LambdaTest URL: https://mobile-hub.lambdatest.com/wd/hub"
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
echo ""
|
|
53
|
+
|
|
54
|
+
# Check for MCP-Appium process
|
|
55
|
+
echo "3. Checking MCP-Appium Server..."
|
|
56
|
+
if pgrep -f "mcp-appium" > /dev/null; then
|
|
57
|
+
echo -e "${GREEN}✓${NC} MCP-Appium process is running"
|
|
58
|
+
echo " PID: $(pgrep -f 'mcp-appium')"
|
|
59
|
+
else
|
|
60
|
+
echo -e "${YELLOW}⚠${NC} MCP-Appium process not found"
|
|
61
|
+
echo " Note: MCP-Appium starts automatically when tests run"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
echo ""
|
|
65
|
+
|
|
66
|
+
# Check if port 3000 (MCP default) is in use
|
|
67
|
+
echo "4. Checking MCP Server Port (3000)..."
|
|
68
|
+
if lsof -Pi :3000 -sTCP:LISTEN -t >/dev/null 2>&1; then
|
|
69
|
+
echo -e "${GREEN}✓${NC} Port 3000 is in use (likely MCP server)"
|
|
70
|
+
echo " Process: $(lsof -Pi :3000 -sTCP:LISTEN | tail -n 1 | awk '{print $1}')"
|
|
71
|
+
else
|
|
72
|
+
echo -e "${YELLOW}⚠${NC} Port 3000 is not in use"
|
|
73
|
+
echo " MCP server will start automatically with tests"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
echo ""
|
|
77
|
+
echo "=========================================="
|
|
78
|
+
echo "Summary"
|
|
79
|
+
echo "=========================================="
|
|
80
|
+
|
|
81
|
+
# Overall status
|
|
82
|
+
ALL_OK=true
|
|
83
|
+
|
|
84
|
+
if ! pgrep -x "ollama" > /dev/null; then
|
|
85
|
+
ALL_OK=false
|
|
86
|
+
echo -e "${RED}✗${NC} Ollama is not running - REQUIRED"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
if [ "$ALL_OK" = true ]; then
|
|
90
|
+
echo -e "${GREEN}✓${NC} System is ready for AI testing!"
|
|
91
|
+
else
|
|
92
|
+
echo -e "${YELLOW}⚠${NC} Some components are not running"
|
|
93
|
+
echo ""
|
|
94
|
+
echo "Quick fixes:"
|
|
95
|
+
echo " - Start Ollama: ollama serve"
|
|
96
|
+
echo " - Pull model: ollama pull llama3.2:3b"
|
|
97
|
+
echo " - MCP starts automatically when running tests"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
echo ""
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extract knowledge from existing Page Object Model files
|
|
5
|
+
* Usage: node scripts/extract-pom-knowledge.js --input <pom-dir> --output <knowledge-file>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
function parseArguments() {
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const config = {
|
|
14
|
+
input: null,
|
|
15
|
+
output: 'knowledge/extracted-pom-knowledge.txt'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
for (let i = 0; i < args.length; i++) {
|
|
19
|
+
if (args[i] === '--input' && args[i + 1]) {
|
|
20
|
+
config.input = args[i + 1];
|
|
21
|
+
i++;
|
|
22
|
+
} else if (args[i] === '--output' && args[i + 1]) {
|
|
23
|
+
config.output = args[i + 1];
|
|
24
|
+
i++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return config;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function extractElementsFromFile(filePath) {
|
|
32
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
33
|
+
const elements = [];
|
|
34
|
+
const screens = [];
|
|
35
|
+
|
|
36
|
+
// Extract screen name from file name
|
|
37
|
+
const fileName = path.basename(filePath, path.extname(filePath));
|
|
38
|
+
const screenName = fileName.replace(/(Android|Ios|\.page)/gi, '');
|
|
39
|
+
screens.push(screenName);
|
|
40
|
+
|
|
41
|
+
// Patterns to match element definitions
|
|
42
|
+
const patterns = [
|
|
43
|
+
// Getter methods: get elementName() { return $('selector') }
|
|
44
|
+
/get\s+(\w+)\s*\(\)\s*\{[^}]*\$\(['"`]([^'"`]+)['"`]\)/g,
|
|
45
|
+
// Direct assignments: this.element = $('selector')
|
|
46
|
+
/this\.(\w+)\s*=\s*\$\(['"`]([^'"`]+)['"`]\)/g,
|
|
47
|
+
// Const/let/var: const element = $('selector')
|
|
48
|
+
/(?:const|let|var)\s+(\w+)\s*=\s*\$\(['"`]([^'"`]+)['"`]\)/g,
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
patterns.forEach(pattern => {
|
|
52
|
+
let match;
|
|
53
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
54
|
+
elements.push({
|
|
55
|
+
name: match[1],
|
|
56
|
+
selector: match[2],
|
|
57
|
+
screen: screenName
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return { elements, screens };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function generateKnowledge(pomDir, outputFile) {
|
|
66
|
+
console.log(`Extracting knowledge from: ${pomDir}`);
|
|
67
|
+
|
|
68
|
+
if (!fs.existsSync(pomDir)) {
|
|
69
|
+
console.error(`Error: Directory not found: ${pomDir}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const allElements = [];
|
|
74
|
+
const allScreens = new Set();
|
|
75
|
+
const platformData = {
|
|
76
|
+
android: [],
|
|
77
|
+
ios: []
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function processDirectory(dir) {
|
|
81
|
+
const files = fs.readdirSync(dir);
|
|
82
|
+
|
|
83
|
+
files.forEach(file => {
|
|
84
|
+
const filePath = path.join(dir, file);
|
|
85
|
+
const stat = fs.statSync(filePath);
|
|
86
|
+
|
|
87
|
+
if (stat.isDirectory()) {
|
|
88
|
+
processDirectory(filePath);
|
|
89
|
+
} else if (file.endsWith('.page.js') || file.endsWith('.page.ts')) {
|
|
90
|
+
const { elements, screens } = extractElementsFromFile(filePath);
|
|
91
|
+
|
|
92
|
+
allElements.push(...elements);
|
|
93
|
+
screens.forEach(s => allScreens.add(s));
|
|
94
|
+
|
|
95
|
+
// Categorize by platform
|
|
96
|
+
const platform = file.toLowerCase().includes('android') ? 'android' :
|
|
97
|
+
file.toLowerCase().includes('ios') ? 'ios' : 'both';
|
|
98
|
+
|
|
99
|
+
if (platform === 'android' || platform === 'both') {
|
|
100
|
+
platformData.android.push(...elements);
|
|
101
|
+
}
|
|
102
|
+
if (platform === 'ios' || platform === 'both') {
|
|
103
|
+
platformData.ios.push(...elements);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
processDirectory(pomDir);
|
|
110
|
+
|
|
111
|
+
console.log(`Found ${allElements.length} elements across ${allScreens.size} screens`);
|
|
112
|
+
|
|
113
|
+
// Generate knowledge file
|
|
114
|
+
const knowledge = [];
|
|
115
|
+
|
|
116
|
+
knowledge.push('# App Knowledge Extracted from Page Object Models');
|
|
117
|
+
knowledge.push('');
|
|
118
|
+
knowledge.push(`Generated: ${new Date().toISOString()}`);
|
|
119
|
+
knowledge.push(`Source: ${pomDir}`);
|
|
120
|
+
knowledge.push('');
|
|
121
|
+
|
|
122
|
+
// Screens overview
|
|
123
|
+
knowledge.push('## Available Screens');
|
|
124
|
+
knowledge.push('');
|
|
125
|
+
Array.from(allScreens).sort().forEach(screen => {
|
|
126
|
+
knowledge.push(`- ${screen}`);
|
|
127
|
+
});
|
|
128
|
+
knowledge.push('');
|
|
129
|
+
|
|
130
|
+
// Group elements by screen
|
|
131
|
+
const elementsByScreen = {};
|
|
132
|
+
allElements.forEach(el => {
|
|
133
|
+
if (!elementsByScreen[el.screen]) {
|
|
134
|
+
elementsByScreen[el.screen] = [];
|
|
135
|
+
}
|
|
136
|
+
elementsByScreen[el.screen].push(el);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
knowledge.push('## Screen Elements and Selectors');
|
|
140
|
+
knowledge.push('');
|
|
141
|
+
|
|
142
|
+
Object.keys(elementsByScreen).sort().forEach(screen => {
|
|
143
|
+
knowledge.push(`### ${screen} Screen`);
|
|
144
|
+
knowledge.push('');
|
|
145
|
+
|
|
146
|
+
const elements = elementsByScreen[screen];
|
|
147
|
+
elements.forEach(el => {
|
|
148
|
+
// Convert camelCase to readable text
|
|
149
|
+
const readable = el.name
|
|
150
|
+
.replace(/([A-Z])/g, ' $1')
|
|
151
|
+
.replace(/^./, str => str.toUpperCase())
|
|
152
|
+
.trim();
|
|
153
|
+
|
|
154
|
+
knowledge.push(`- **${readable}**: \`${el.selector}\``);
|
|
155
|
+
});
|
|
156
|
+
knowledge.push('');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Common patterns
|
|
160
|
+
knowledge.push('## Common Selector Patterns');
|
|
161
|
+
knowledge.push('');
|
|
162
|
+
|
|
163
|
+
const selectorPatterns = {
|
|
164
|
+
'Resource ID': allElements.filter(e => e.selector.includes('resource-id')),
|
|
165
|
+
'Accessibility ID': allElements.filter(e => e.selector.includes('accessibility id') || e.selector.includes('~')),
|
|
166
|
+
'Text': allElements.filter(e => e.selector.includes('@text') || e.selector.includes('text=')),
|
|
167
|
+
'XPath': allElements.filter(e => e.selector.startsWith('//')),
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
Object.keys(selectorPatterns).forEach(pattern => {
|
|
171
|
+
const count = selectorPatterns[pattern].length;
|
|
172
|
+
if (count > 0) {
|
|
173
|
+
knowledge.push(`- **${pattern}**: ${count} elements use this strategy`);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
knowledge.push('');
|
|
177
|
+
|
|
178
|
+
// Platform-specific notes
|
|
179
|
+
knowledge.push('## Platform-Specific Information');
|
|
180
|
+
knowledge.push('');
|
|
181
|
+
knowledge.push(`### Android`);
|
|
182
|
+
knowledge.push(`- Total elements: ${platformData.android.length}`);
|
|
183
|
+
knowledge.push(`- Common selectors: resource-id, text, xpath`);
|
|
184
|
+
knowledge.push('');
|
|
185
|
+
knowledge.push(`### iOS`);
|
|
186
|
+
knowledge.push(`- Total elements: ${platformData.ios.length}`);
|
|
187
|
+
knowledge.push(`- Common selectors: accessibility-id, name, label`);
|
|
188
|
+
knowledge.push('');
|
|
189
|
+
|
|
190
|
+
// Tips for AI
|
|
191
|
+
knowledge.push('## Tips for AI Agent');
|
|
192
|
+
knowledge.push('');
|
|
193
|
+
knowledge.push('1. Prefer using accessibility IDs when available');
|
|
194
|
+
knowledge.push('2. Text-based selectors are reliable for buttons and labels');
|
|
195
|
+
knowledge.push('3. Resource IDs (Android) are stable across app versions');
|
|
196
|
+
knowledge.push('4. Use XPath only as a last resort');
|
|
197
|
+
knowledge.push('5. Elements may have slight variations across different device sizes');
|
|
198
|
+
knowledge.push('');
|
|
199
|
+
|
|
200
|
+
// Write to file
|
|
201
|
+
const outputDir = path.dirname(outputFile);
|
|
202
|
+
if (!fs.existsSync(outputDir)) {
|
|
203
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fs.writeFileSync(outputFile, knowledge.join('\n'));
|
|
207
|
+
console.log(`Knowledge file generated: ${outputFile}`);
|
|
208
|
+
console.log(`Summary: ${allElements.length} elements, ${allScreens.size} screens`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Main execution
|
|
212
|
+
const config = parseArguments();
|
|
213
|
+
|
|
214
|
+
if (!config.input) {
|
|
215
|
+
console.error('Usage: node extract-pom-knowledge.js --input <pom-directory> [--output <output-file>]');
|
|
216
|
+
console.error('');
|
|
217
|
+
console.error('Example:');
|
|
218
|
+
console.error(' node extract-pom-knowledge.js --input ../dmg.qa.test.technician-app-3/pages --output knowledge/app-knowledge.txt');
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
generateKnowledge(config.input, config.output);
|