@xenon-device-management/xenon 1.1.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/README.md +446 -0
- package/lib/package.json +207 -0
- package/lib/public/assets/Layouts-7IT8aFLI.js +11 -0
- package/lib/public/assets/Layouts-DPMls9vh.css +1 -0
- package/lib/public/assets/ai-settings-BbnfgdEx.js +11 -0
- package/lib/public/assets/apps-CRMrI4_p.js +16 -0
- package/lib/public/assets/apps-CcM77dgg.css +1 -0
- package/lib/public/assets/badge-B1nKs8zj.css +1 -0
- package/lib/public/assets/badge-CSvl5xIU.js +11 -0
- package/lib/public/assets/button-CJlKn4PZ.css +1 -0
- package/lib/public/assets/button-CvLaGFYj.js +26 -0
- package/lib/public/assets/calendar-6w-D6Oaw.js +6 -0
- package/lib/public/assets/clock-DcdeWBPr.js +6 -0
- package/lib/public/assets/cpu-DiSoXT9n.js +6 -0
- package/lib/public/assets/device-explorer-CajM63OJ.js +193 -0
- package/lib/public/assets/device-explorer-CxdUAoTL.css +1 -0
- package/lib/public/assets/index-ByQwMN5T.js +174 -0
- package/lib/public/assets/index-C1DBaoSh.js +1 -0
- package/lib/public/assets/index-qzCez_kk.css +1 -0
- package/lib/public/assets/lock-B23ibZmo.js +6 -0
- package/lib/public/assets/maintenance-settings-CirzA6yG.js +6 -0
- package/lib/public/assets/mouse-pointer-2-Cz76SHFb.js +6 -0
- package/lib/public/assets/plus-BBwlIevt.js +6 -0
- package/lib/public/assets/session-dashboard-C2k7FFv_.css +1 -0
- package/lib/public/assets/session-dashboard-HPDtwPOZ.js +62 -0
- package/lib/public/assets/settings-DrZsZwdc.js +1 -0
- package/lib/public/assets/trash-2-DQpvzJec.js +6 -0
- package/lib/public/assets/useSocket-Dxsqae2a.js +16 -0
- package/lib/public/assets/webhook-settings-CDPgsgkb.css +1 -0
- package/lib/public/assets/webhook-settings-Cp-B4Nrw.js +1 -0
- package/lib/public/assets/zap-DovP6iow.js +6 -0
- package/lib/public/favicon.ico +0 -0
- package/lib/public/favicon.png +0 -0
- package/lib/public/favicon.svg +9 -0
- package/lib/public/index.html +46 -0
- package/lib/public/logo.svg +17 -0
- package/lib/public/logo192.png +0 -0
- package/lib/public/logo512.png +0 -0
- package/lib/public/manifest.json +25 -0
- package/lib/public/robots.txt +3 -0
- package/lib/schema.json +348 -0
- package/lib/src/InternalHttpClient.js +212 -0
- package/lib/src/PluginContext.js +29 -0
- package/lib/src/XenonCapabilityManager.js +199 -0
- package/lib/src/app/index.js +167 -0
- package/lib/src/app/routers/apps.js +79 -0
- package/lib/src/app/routers/config.js +131 -0
- package/lib/src/app/routers/control.js +835 -0
- package/lib/src/app/routers/dashboard.js +301 -0
- package/lib/src/app/routers/grid.js +352 -0
- package/lib/src/app/routers/reservation.js +190 -0
- package/lib/src/app/routers/webhook.js +83 -0
- package/lib/src/app/swagger-docs.js +203 -0
- package/lib/src/app/swagger.js +366 -0
- package/lib/src/chromeUtils.js +148 -0
- package/lib/src/commands/handle.js +19 -0
- package/lib/src/commands/index.js +8 -0
- package/lib/src/config.js +73 -0
- package/lib/src/dashboard/asset-manager.js +84 -0
- package/lib/src/dashboard/commands.js +284 -0
- package/lib/src/dashboard/event-manager.js +699 -0
- package/lib/src/dashboard/services/app-service.js +134 -0
- package/lib/src/dashboard/services/failure-analysis-service.js +173 -0
- package/lib/src/dashboard/services/session-service.js +113 -0
- package/lib/src/data-service/CircuitBreaker.js +83 -0
- package/lib/src/data-service/config-service.js +155 -0
- package/lib/src/data-service/db.js +122 -0
- package/lib/src/data-service/device-service.js +320 -0
- package/lib/src/data-service/device-store.interface.js +2 -0
- package/lib/src/data-service/device-store.js +345 -0
- package/lib/src/data-service/pending-sessions-service.js +25 -0
- package/lib/src/data-service/pluginArgs.js +25 -0
- package/lib/src/data-service/prisma-service.js +31 -0
- package/lib/src/data-service/prisma-store.js +385 -0
- package/lib/src/data-service/queue-service.js +150 -0
- package/lib/src/data-service/web-config-service.js +130 -0
- package/lib/src/device-managers/AndroidDeviceManager.js +1155 -0
- package/lib/src/device-managers/ChromeDriverManager.js +68 -0
- package/lib/src/device-managers/HealthMonitorService.js +325 -0
- package/lib/src/device-managers/IOSDeviceManager.js +351 -0
- package/lib/src/device-managers/NodeDevices.js +82 -0
- package/lib/src/device-managers/android/AndroidStreamService.js +370 -0
- package/lib/src/device-managers/android/DeviceLockManager.js +45 -0
- package/lib/src/device-managers/cloud/CapabilityManager.js +26 -0
- package/lib/src/device-managers/cloud/Devices.js +86 -0
- package/lib/src/device-managers/iOSTracker.js +44 -0
- package/lib/src/device-managers/index.js +89 -0
- package/lib/src/device-managers/ios/IOSDiscoveryService.js +268 -0
- package/lib/src/device-managers/ios/IOSStreamService.js +893 -0
- package/lib/src/device-managers/ios/WDAClient.js +866 -0
- package/lib/src/device-utils.js +663 -0
- package/lib/src/enums/Capabilities.js +8 -0
- package/lib/src/enums/Cloud.js +11 -0
- package/lib/src/enums/Platform.js +9 -0
- package/lib/src/enums/SessionType.js +9 -0
- package/lib/src/enums/SocketEvents.js +15 -0
- package/lib/src/helpers/UniversalMjpegProxy.js +273 -0
- package/lib/src/helpers/index.js +229 -0
- package/lib/src/index.js +95 -0
- package/lib/src/interceptors/CommandInterceptor.js +524 -0
- package/lib/src/interfaces/ICloudManager.js +2 -0
- package/lib/src/interfaces/IDevice.js +2 -0
- package/lib/src/interfaces/IDeviceFilterOptions.js +2 -0
- package/lib/src/interfaces/IDeviceManager.js +2 -0
- package/lib/src/interfaces/IOptions.js +2 -0
- package/lib/src/interfaces/IPluginArgs.js +55 -0
- package/lib/src/interfaces/ISessionCapability.js +2 -0
- package/lib/src/logger.js +225 -0
- package/lib/src/plugin.js +244 -0
- package/lib/src/prisma.js +12 -0
- package/lib/src/profiling/AndroidAppProfiler.js +213 -0
- package/lib/src/proxy/wd-command-proxy.js +221 -0
- package/lib/src/scripts/generate-database-migration.js +59 -0
- package/lib/src/scripts/initialize-database.js +55 -0
- package/lib/src/scripts/install-go-ios.js +66 -0
- package/lib/src/scripts/prepare-prisma.js +89 -0
- package/lib/src/services/AICommandService.js +143 -0
- package/lib/src/services/AIService.js +466 -0
- package/lib/src/services/CleanupService.js +141 -0
- package/lib/src/services/EventBus.js +74 -0
- package/lib/src/services/InspectorService.js +395 -0
- package/lib/src/services/MetricsService.js +134 -0
- package/lib/src/services/NetworkConditioningService.js +173 -0
- package/lib/src/services/NotificationService.js +163 -0
- package/lib/src/services/RequestLogService.js +252 -0
- package/lib/src/services/ResourceIsolationService.js +122 -0
- package/lib/src/services/SecurityService.js +120 -0
- package/lib/src/services/ServerManager.js +284 -0
- package/lib/src/services/SessionHeartbeatService.js +158 -0
- package/lib/src/services/SessionLifecycleService.js +572 -0
- package/lib/src/services/SocketClient.js +71 -0
- package/lib/src/services/SocketServer.js +87 -0
- package/lib/src/services/TracingService.js +132 -0
- package/lib/src/services/VideoPipelineService.js +220 -0
- package/lib/src/services/healing/FuzzyXmlHealingProvider.js +333 -0
- package/lib/src/services/healing/HealEtalonService.js +98 -0
- package/lib/src/services/healing/HealedLocatorGenerator.js +132 -0
- package/lib/src/services/healing/HealingOrchestrator.js +165 -0
- package/lib/src/services/healing/LlmHealingProvider.js +77 -0
- package/lib/src/services/healing/OcrHealingProvider.js +119 -0
- package/lib/src/services/healing/ResilioTreeHealingProvider.js +100 -0
- package/lib/src/services/healing/VisualAiHealingProvider.js +90 -0
- package/lib/src/services/healing/types.js +12 -0
- package/lib/src/services/omni-vision/OmniVisionService.js +718 -0
- package/lib/src/services/omni-vision/VisionAssertionService.js +68 -0
- package/lib/src/sessions/CloudSession.js +42 -0
- package/lib/src/sessions/LocalSession.js +313 -0
- package/lib/src/sessions/RemoteSession.js +287 -0
- package/lib/src/sessions/SessionManager.js +238 -0
- package/lib/src/sessions/XenonSession.js +44 -0
- package/lib/src/types/CLIArgs.js +2 -0
- package/lib/src/types/CloudArgs.js +2 -0
- package/lib/src/types/CloudSchema.js +131 -0
- package/lib/src/types/DeviceType.js +2 -0
- package/lib/src/types/DeviceUpdate.js +2 -0
- package/lib/src/types/IOSDevice.js +2 -0
- package/lib/src/types/Platform.js +2 -0
- package/lib/src/types/SessionStatus.js +11 -0
- package/lib/src/validators/CapabilityValidator.js +93 -0
- package/lib/test/e2e/android/conf.spec.js +43 -0
- package/lib/test/e2e/android/conf2.spec.js +44 -0
- package/lib/test/e2e/android/conf3.spec.js +44 -0
- package/lib/test/e2e/e2ehelper.js +113 -0
- package/lib/test/e2e/hubnode/forward-request.spec.js +224 -0
- package/lib/test/e2e/hubnode/hubnode.spec.js +214 -0
- package/lib/test/e2e/ios/conf1.spec.js +39 -0
- package/lib/test/e2e/ios/conf2.spec.js +39 -0
- package/lib/test/e2e/plugin-harness.js +236 -0
- package/lib/test/e2e/plugin.spec.js +97 -0
- package/lib/test/e2e/telemetry_verification.spec.js +83 -0
- package/lib/test/e2e/video-recording-test.spec.js +63 -0
- package/lib/test/helpers/test-container.js +112 -0
- package/lib/test/integration/androidDevices.spec.js +137 -0
- package/lib/test/integration/cliArgs.js +73 -0
- package/lib/test/integration/ios/01iOSSimulator.spec.js +291 -0
- package/lib/test/integration/ios/02iOSDevices.spec.js +75 -0
- package/lib/test/integration/testHelpers.js +74 -0
- package/lib/test/unit/AndroidDeviceManager.spec.js +178 -0
- package/lib/test/unit/ChromeDriverManager.spec.js +26 -0
- package/lib/test/unit/CleanupService.spec.js +21 -0
- package/lib/test/unit/DeviceModel.spec.js +157 -0
- package/lib/test/unit/FuzzyXmlHealingProvider.test.js +294 -0
- package/lib/test/unit/GetAdbOriginal.js +42 -0
- package/lib/test/unit/HealingCascade.test.js +128 -0
- package/lib/test/unit/IOSDeviceManager.spec.js +261 -0
- package/lib/test/unit/RemoteIOs.spec.js +78 -0
- package/lib/test/unit/ResilioTreeHealingProvider.test.js +96 -0
- package/lib/test/unit/commands.spec.js +27 -0
- package/lib/test/unit/config.spec.js +27 -0
- package/lib/test/unit/device-service.spec.js +307 -0
- package/lib/test/unit/device-utils.spec.js +313 -0
- package/lib/test/unit/fixtures/device.config.js +4 -0
- package/lib/test/unit/fixtures/devices.js +89 -0
- package/lib/test/unit/helpers.spec.js +62 -0
- package/lib/test/unit/omni-vision.spec.js +100 -0
- package/lib/test/unit/plugin.spec.js +133 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/package.json +207 -0
- package/prisma/data.db +0 -0
- package/prisma/dev.db +0 -0
- package/prisma/dev.db-journal +0 -0
- package/prisma/migrations/20231011074725_initial_tables/migration.sql +47 -0
- package/prisma/migrations/20231226115334_update_session_log/migration.sql +2 -0
- package/prisma/migrations/20251204113710_add_video_recording_enabled/migration.sql +29 -0
- package/prisma/migrations/20251204132449_add_log_table/migration.sql +11 -0
- package/prisma/migrations/20251205050111_add_profiling_support/migration.sql +47 -0
- package/prisma/migrations/20251205050947_add_is_error_field/migration.sql +24 -0
- package/prisma/migrations/20260126201337_add_app_model/migration.sql +18 -0
- package/prisma/migrations/20260130115722_add_performance_trace_and_xenon_sync/migration.sql +2 -0
- package/prisma/migrations/20260130135114_add_device_models/migration.sql +57 -0
- package/prisma/migrations/20260130140655_make_systemport_optional/migration.sql +45 -0
- package/prisma/migrations/20260130140932_make_device_fields_optional/migration.sql +45 -0
- package/prisma/migrations/20260130141040_final_schema_fix/migration.sql +45 -0
- package/prisma/migrations/20260130143234_add_device_health_fields/migration.sql +4 -0
- package/prisma/migrations/20260130144921_add_failure_category/migration.sql +2 -0
- package/prisma/migrations/20260131151456_add_webhook_config/migration.sql +10 -0
- package/prisma/migrations/20260201094507_add_device_tags/migration.sql +11 -0
- package/prisma/migrations/20260201103410_add_managed_process/migration.sql +15 -0
- package/prisma/migrations/20260201140637_add_web_config/migration.sql +22 -0
- package/prisma/migrations/20260201162232_add_session_progress/migration.sql +2 -0
- package/prisma/migrations/20260201174231_add_total_healed_count/migration.sql +2 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +210 -0
- package/schema.json +348 -0
- package/scripts/build-xenon.sh +32 -0
- package/scripts/dev/debug-gemini.ts +44 -0
- package/scripts/generate-types-from-schema.js +86 -0
- package/scripts/install-compatible-driver.js +39 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
15
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
16
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
17
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
18
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
19
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
20
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
24
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.InspectorService = void 0;
|
|
28
|
+
const typedi_1 = require("typedi");
|
|
29
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
30
|
+
const PluginContext_1 = require("../PluginContext");
|
|
31
|
+
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
32
|
+
let InspectorService = class InspectorService {
|
|
33
|
+
constructor(context) {
|
|
34
|
+
this.context = context;
|
|
35
|
+
this.log = logger_1.default.scope('Inspector');
|
|
36
|
+
this.parser = new fast_xml_parser_1.XMLParser({
|
|
37
|
+
ignoreAttributes: false,
|
|
38
|
+
attributeNamePrefix: '',
|
|
39
|
+
parseAttributeValue: true,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
getSnapshot(udid) {
|
|
43
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
try {
|
|
45
|
+
this.log.info(`Generating deep snapshot for device: ${udid}`);
|
|
46
|
+
// 1. Get Device Manager
|
|
47
|
+
const device = yield typedi_1.Container.get('DeviceStore').findDevice({ udid });
|
|
48
|
+
if (!device)
|
|
49
|
+
throw new Error(`Device ${udid} not found`);
|
|
50
|
+
let manager;
|
|
51
|
+
if (device.platform === 'android') {
|
|
52
|
+
manager = typedi_1.Container.get('AndroidDeviceManager');
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
manager = typedi_1.Container.get('IOSDeviceManager');
|
|
56
|
+
}
|
|
57
|
+
// 2. Parallel Capture (Native Source + Screenshot)
|
|
58
|
+
const [screenshotBase64, xmlSource] = yield Promise.all([
|
|
59
|
+
manager.getScreenshot
|
|
60
|
+
? manager.getScreenshot(udid)
|
|
61
|
+
: Promise.reject('Screenshot not supported'),
|
|
62
|
+
manager.getPageSource
|
|
63
|
+
? manager.getPageSource(udid)
|
|
64
|
+
: Promise.reject('Page source not supported'),
|
|
65
|
+
]);
|
|
66
|
+
// 3. Ensure Metadata (Lazy load dimensions if missing)
|
|
67
|
+
if (!device.screenWidth || !device.screenHeight) {
|
|
68
|
+
try {
|
|
69
|
+
if (manager.getAdditionalDeviceInfo) {
|
|
70
|
+
const additional = yield manager.getAdditionalDeviceInfo(device);
|
|
71
|
+
Object.assign(device, additional);
|
|
72
|
+
// Update store asynchronously
|
|
73
|
+
typedi_1.Container.get('DeviceStore')
|
|
74
|
+
.updateDevice(udid, device.host, additional)
|
|
75
|
+
.catch(() => { });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
this.log.warn(`Failed to lazy-load dimensions for ${udid}: ${e}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// 4. Parse Native Hierarchy
|
|
83
|
+
const hierarchy = this.parseXmlHierarchy(xmlSource, device.platform);
|
|
84
|
+
return {
|
|
85
|
+
udid,
|
|
86
|
+
platform: device.platform,
|
|
87
|
+
timestamp: new Date().toISOString(),
|
|
88
|
+
screenshot: screenshotBase64,
|
|
89
|
+
hierarchy,
|
|
90
|
+
metadata: {
|
|
91
|
+
screenWidth: parseInt(device.screenWidth || '0') || 393, // Fallback to common iPhone width
|
|
92
|
+
screenHeight: parseInt(device.screenHeight || '0') || 852,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
this.log.error(`Failed to generate deep snapshot for ${udid}: ${err.message}`);
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
parseXmlHierarchy(xml, platform) {
|
|
103
|
+
var _a;
|
|
104
|
+
try {
|
|
105
|
+
const jsonObj = this.parser.parse(xml);
|
|
106
|
+
if (platform === 'android') {
|
|
107
|
+
// Android hierarchy usually starts with <hierarchy>
|
|
108
|
+
const root = ((_a = jsonObj.hierarchy) === null || _a === void 0 ? void 0 : _a.node) || jsonObj.hierarchy;
|
|
109
|
+
return this.transformAndroidNode(root, '');
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// iOS hierarchy usually starts with <AppiumAUT>
|
|
113
|
+
const root = jsonObj.AppiumAUT || jsonObj;
|
|
114
|
+
return this.transformIosNode(root, '');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
this.log.error(`Failed to parse XML hierarchy: ${e.message}`);
|
|
119
|
+
return {
|
|
120
|
+
name: 'root',
|
|
121
|
+
type: 'error',
|
|
122
|
+
rect: { x: 0, y: 0, width: 0, height: 0 },
|
|
123
|
+
xpath: '/',
|
|
124
|
+
children: [],
|
|
125
|
+
attributes: {},
|
|
126
|
+
suggestedLocators: [],
|
|
127
|
+
suggestedActions: [],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
transformAndroidNode(node, root) {
|
|
132
|
+
var _a;
|
|
133
|
+
const type = node.class || 'Unknown';
|
|
134
|
+
const name = node.resourceId || node.text || type.split('.').pop() || 'node';
|
|
135
|
+
const bounds = this.parseAndroidBounds(node.bounds || '[0,0][0,0]');
|
|
136
|
+
const xpath = node.xpath || ''; // Placeholder for now, real xpath handled by generator
|
|
137
|
+
const children = Array.isArray(node.node)
|
|
138
|
+
? node.node.map((c) => this.transformAndroidNode(c, root))
|
|
139
|
+
: node.node
|
|
140
|
+
? [this.transformAndroidNode(node.node, root)]
|
|
141
|
+
: [];
|
|
142
|
+
const attributes = {};
|
|
143
|
+
Object.entries(node).forEach(([key, value]) => {
|
|
144
|
+
// Filter out internal structural keys and non-primitive values
|
|
145
|
+
if (key !== 'node' &&
|
|
146
|
+
key !== 'xpath' &&
|
|
147
|
+
(typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')) {
|
|
148
|
+
attributes[key] = value;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
const nodeObj = {
|
|
152
|
+
name,
|
|
153
|
+
type,
|
|
154
|
+
text: node.text,
|
|
155
|
+
rect: bounds,
|
|
156
|
+
xpath,
|
|
157
|
+
suggestedLocators: [],
|
|
158
|
+
suggestedActions: [],
|
|
159
|
+
children,
|
|
160
|
+
attributes,
|
|
161
|
+
};
|
|
162
|
+
nodeObj.suggestedLocators = this.generateLocators(nodeObj, 'android', root || node);
|
|
163
|
+
nodeObj.suggestedActions = this.generateActions(nodeObj);
|
|
164
|
+
nodeObj.xpath = ((_a = nodeObj.suggestedLocators.find((l) => l.strategy === 'xpath')) === null || _a === void 0 ? void 0 : _a.value) || '/';
|
|
165
|
+
return nodeObj;
|
|
166
|
+
}
|
|
167
|
+
transformIosNode(node, root) {
|
|
168
|
+
var _a;
|
|
169
|
+
const type = Object.keys(node).find((k) => k.startsWith('XCUIElement')) || 'Unknown';
|
|
170
|
+
const data = node[type];
|
|
171
|
+
const name = (data === null || data === void 0 ? void 0 : data.name) || (data === null || data === void 0 ? void 0 : data.label) || type;
|
|
172
|
+
const rect = {
|
|
173
|
+
x: (data === null || data === void 0 ? void 0 : data.x) || 0,
|
|
174
|
+
y: (data === null || data === void 0 ? void 0 : data.y) || 0,
|
|
175
|
+
width: (data === null || data === void 0 ? void 0 : data.width) || 0,
|
|
176
|
+
height: (data === null || data === void 0 ? void 0 : data.height) || 0,
|
|
177
|
+
};
|
|
178
|
+
const children = [];
|
|
179
|
+
Object.keys(data || {}).forEach((key) => {
|
|
180
|
+
if (key.startsWith('XCUIElement')) {
|
|
181
|
+
const childData = data[key];
|
|
182
|
+
if (Array.isArray(childData)) {
|
|
183
|
+
childData.forEach((c) => children.push(this.transformIosNode({ [key]: c }, root)));
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
children.push(this.transformIosNode({ [key]: childData }, root));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
const attributes = {};
|
|
191
|
+
Object.entries(data || {}).forEach(([key, value]) => {
|
|
192
|
+
// Filter out nested element keys (XCUIElement...) and non-primitive values
|
|
193
|
+
if (!key.startsWith('XCUIElement') &&
|
|
194
|
+
(typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean')) {
|
|
195
|
+
attributes[key] = value;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
const nodeObj = {
|
|
199
|
+
name,
|
|
200
|
+
type,
|
|
201
|
+
text: (data === null || data === void 0 ? void 0 : data.label) || (data === null || data === void 0 ? void 0 : data.value),
|
|
202
|
+
rect,
|
|
203
|
+
xpath: '',
|
|
204
|
+
suggestedLocators: [],
|
|
205
|
+
suggestedActions: [],
|
|
206
|
+
children,
|
|
207
|
+
attributes,
|
|
208
|
+
};
|
|
209
|
+
nodeObj.suggestedLocators = this.generateLocators(nodeObj, 'ios', root || node);
|
|
210
|
+
nodeObj.suggestedActions = this.generateActions(nodeObj);
|
|
211
|
+
nodeObj.xpath = ((_a = nodeObj.suggestedLocators.find((l) => l.strategy === 'xpath')) === null || _a === void 0 ? void 0 : _a.value) || '/';
|
|
212
|
+
return nodeObj;
|
|
213
|
+
}
|
|
214
|
+
generateActions(node) {
|
|
215
|
+
const actions = [];
|
|
216
|
+
const type = node.type.toLowerCase();
|
|
217
|
+
// Common clickable elements
|
|
218
|
+
if (type.includes('button') ||
|
|
219
|
+
type.includes('imageview') ||
|
|
220
|
+
type.includes('icon') ||
|
|
221
|
+
node.attributes.clickable === 'true' ||
|
|
222
|
+
node.attributes.enabled === 'true') {
|
|
223
|
+
actions.push({
|
|
224
|
+
action: 'click',
|
|
225
|
+
snippet: 'await element.click();',
|
|
226
|
+
description: 'Performs a single tap interaction',
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
// Common input elements
|
|
230
|
+
if (type.includes('edit') ||
|
|
231
|
+
type.includes('textfield') ||
|
|
232
|
+
type.includes('input') ||
|
|
233
|
+
type.includes('search')) {
|
|
234
|
+
actions.push({
|
|
235
|
+
action: 'sendKeys',
|
|
236
|
+
snippet: 'await element.sendKeys("value");',
|
|
237
|
+
description: 'Inputs text into the element',
|
|
238
|
+
});
|
|
239
|
+
actions.push({
|
|
240
|
+
action: 'clear',
|
|
241
|
+
snippet: 'await element.clear();',
|
|
242
|
+
description: 'Clears the text content',
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
return actions;
|
|
246
|
+
}
|
|
247
|
+
generateLocators(node, platform, root) {
|
|
248
|
+
const suggestions = [];
|
|
249
|
+
// 1. Accessibility ID - GOLD STANDARD
|
|
250
|
+
// Android: content-desc | iOS: name/accessibility-id
|
|
251
|
+
const contentDesc = node.attributes.contentDescription || node.attributes['content-desc'];
|
|
252
|
+
const iosName = node.attributes.name || node.attributes.label;
|
|
253
|
+
if (contentDesc || iosName) {
|
|
254
|
+
const val = contentDesc || iosName;
|
|
255
|
+
suggestions.push({
|
|
256
|
+
strategy: 'accessibility id',
|
|
257
|
+
value: val,
|
|
258
|
+
unique: this.checkUniqueness(root, 'accessibility id', val),
|
|
259
|
+
score: 100,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// 2. Resource ID / ID
|
|
263
|
+
// Android: resource-id | iOS: identifier/name
|
|
264
|
+
const resId = node.attributes.resourceId || node.attributes['resource-id'] || node.attributes.identifier;
|
|
265
|
+
if (resId) {
|
|
266
|
+
suggestions.push({
|
|
267
|
+
strategy: 'id',
|
|
268
|
+
value: resId,
|
|
269
|
+
unique: this.checkUniqueness(root, 'id', resId),
|
|
270
|
+
score: 95,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
// 3. Platform Specific - Performance Tier
|
|
274
|
+
if (platform === 'ios') {
|
|
275
|
+
// iOS Predicate String - High Performance
|
|
276
|
+
const predicate = `type == "${node.type}" AND label == "${node.text || node.label || ''}"`;
|
|
277
|
+
if (node.text || node.label) {
|
|
278
|
+
suggestions.push({
|
|
279
|
+
strategy: '-ios predicate string',
|
|
280
|
+
value: predicate,
|
|
281
|
+
unique: true,
|
|
282
|
+
score: 90,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
// iOS Class Chain - High Precision
|
|
286
|
+
const classChain = `**/${node.type}[` +
|
|
287
|
+
(node.attributes.name
|
|
288
|
+
? `name == "${node.attributes.name}"`
|
|
289
|
+
: `label == "${node.text || ''}"`) +
|
|
290
|
+
`]`;
|
|
291
|
+
suggestions.push({
|
|
292
|
+
strategy: '-ios class chain',
|
|
293
|
+
value: classChain,
|
|
294
|
+
unique: true,
|
|
295
|
+
score: 85,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
// Android UIAutomator - Reliable Native
|
|
300
|
+
if (resId) {
|
|
301
|
+
suggestions.push({
|
|
302
|
+
strategy: '-android uiautomator',
|
|
303
|
+
value: `new UiSelector().resourceId("${resId}")`,
|
|
304
|
+
unique: true,
|
|
305
|
+
score: 88,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
if (node.text) {
|
|
309
|
+
suggestions.push({
|
|
310
|
+
strategy: '-android uiautomator',
|
|
311
|
+
value: `new UiSelector().text("${node.text}")`,
|
|
312
|
+
unique: this.checkUniqueness(root, 'text', node.text),
|
|
313
|
+
score: 80,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// 4. XPath - Stable Semantic version
|
|
318
|
+
// Use tag name from the last part of Class name
|
|
319
|
+
const tagName = node.type.includes('.') ? node.type.split('.').pop() : node.type;
|
|
320
|
+
if (resId) {
|
|
321
|
+
suggestions.push({
|
|
322
|
+
strategy: 'xpath',
|
|
323
|
+
value: `//${tagName}[@resource-id="${resId}"]`,
|
|
324
|
+
unique: true,
|
|
325
|
+
score: 70,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
else if (node.text) {
|
|
329
|
+
suggestions.push({
|
|
330
|
+
strategy: 'xpath',
|
|
331
|
+
value: `//${tagName}[@text="${node.text}"]`,
|
|
332
|
+
unique: this.checkUniqueness(root, 'xpath', `//${tagName}[@text="${node.text}"]`),
|
|
333
|
+
score: 60,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
else if (contentDesc) {
|
|
337
|
+
suggestions.push({
|
|
338
|
+
strategy: 'xpath',
|
|
339
|
+
value: `//${tagName}[@content-desc="${contentDesc}"]`,
|
|
340
|
+
unique: true,
|
|
341
|
+
score: 65,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return suggestions;
|
|
345
|
+
}
|
|
346
|
+
checkUniqueness(root, strategy, value) {
|
|
347
|
+
let matches = 0;
|
|
348
|
+
const search = (n) => {
|
|
349
|
+
const node = n.node || n;
|
|
350
|
+
if (strategy === 'id') {
|
|
351
|
+
if (node.resourceId === value || node['resource-id'] === value || node.identifier === value)
|
|
352
|
+
matches++;
|
|
353
|
+
}
|
|
354
|
+
else if (strategy === 'accessibility id') {
|
|
355
|
+
if (node.accessibilityId === value ||
|
|
356
|
+
node['content-desc'] === value ||
|
|
357
|
+
node.contentDescription === value ||
|
|
358
|
+
node.name === value)
|
|
359
|
+
matches++;
|
|
360
|
+
}
|
|
361
|
+
else if (strategy === 'text') {
|
|
362
|
+
if (node.text === value || node.label === value)
|
|
363
|
+
matches++;
|
|
364
|
+
}
|
|
365
|
+
if (matches > 1)
|
|
366
|
+
return;
|
|
367
|
+
const children = n.node || [];
|
|
368
|
+
if (Array.isArray(children))
|
|
369
|
+
children.forEach(search);
|
|
370
|
+
else if (n.children && Array.isArray(n.children))
|
|
371
|
+
n.children.forEach(search);
|
|
372
|
+
else if (children && typeof children === 'object')
|
|
373
|
+
search(children);
|
|
374
|
+
};
|
|
375
|
+
search(root);
|
|
376
|
+
return matches === 1;
|
|
377
|
+
}
|
|
378
|
+
parseAndroidBounds(boundsStr) {
|
|
379
|
+
const matches = /\[(\d+),(\d+)\]\[(\d+),(\d+)\]/.exec(boundsStr);
|
|
380
|
+
if (matches) {
|
|
381
|
+
const x1 = parseInt(matches[1]);
|
|
382
|
+
const y1 = parseInt(matches[2]);
|
|
383
|
+
const x2 = parseInt(matches[3]);
|
|
384
|
+
const y2 = parseInt(matches[4]);
|
|
385
|
+
return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
|
|
386
|
+
}
|
|
387
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
exports.InspectorService = InspectorService;
|
|
391
|
+
exports.InspectorService = InspectorService = __decorate([
|
|
392
|
+
(0, typedi_1.Service)(),
|
|
393
|
+
__param(0, (0, typedi_1.Inject)()),
|
|
394
|
+
__metadata("design:paramtypes", [PluginContext_1.PluginContext])
|
|
395
|
+
], InspectorService);
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
10
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
11
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
12
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
13
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
14
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
18
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.MetricsService = void 0;
|
|
22
|
+
const typedi_1 = require("typedi");
|
|
23
|
+
const SessionManager_1 = require("../sessions/SessionManager");
|
|
24
|
+
const device_store_1 = require("../data-service/device-store");
|
|
25
|
+
const prisma_1 = require("../prisma");
|
|
26
|
+
const logger_1 = __importDefault(require("../logger"));
|
|
27
|
+
let MetricsService = class MetricsService {
|
|
28
|
+
constructor() {
|
|
29
|
+
this.CONFIG_ID = 'metrics';
|
|
30
|
+
}
|
|
31
|
+
incrementMetric(name) {
|
|
32
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
var _a;
|
|
34
|
+
try {
|
|
35
|
+
const config = yield prisma_1.prisma.webConfig.findUnique({
|
|
36
|
+
where: { name },
|
|
37
|
+
});
|
|
38
|
+
const currentValue = config ? parseInt(config.value) : 0;
|
|
39
|
+
yield prisma_1.prisma.webConfig.upsert({
|
|
40
|
+
where: { name },
|
|
41
|
+
update: { value: (currentValue + 1).toString() },
|
|
42
|
+
create: { id: name, name, value: '1' },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const msg = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : String(error);
|
|
47
|
+
logger_1.default.error(`[MetricsService] Failed to increment ${name}: ${msg}`, error);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
getMetric(name) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
try {
|
|
54
|
+
const config = yield prisma_1.prisma.webConfig.findUnique({
|
|
55
|
+
where: { name },
|
|
56
|
+
});
|
|
57
|
+
return config ? parseInt(config.value) : 0;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
incrementSessionStart() {
|
|
65
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
yield this.incrementMetric('metric_session_starts');
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
incrementSessionSuccess() {
|
|
70
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
71
|
+
yield this.incrementMetric('metric_session_successes');
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
incrementSessionFailure() {
|
|
75
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
yield this.incrementMetric('metric_session_failures');
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
incrementHealingAttempt() {
|
|
80
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
81
|
+
yield this.incrementMetric('metric_healing_attempts');
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
incrementHealingSuccess() {
|
|
85
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
yield this.incrementMetric('metric_healing_successes');
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
getMetrics() {
|
|
90
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
const sessions = SessionManager_1.SESSION_MANAGER.getStats();
|
|
92
|
+
const devices = yield device_store_1.DeviceStoreFactory.getStore().getAllDevices();
|
|
93
|
+
const totalDevices = devices.length;
|
|
94
|
+
const busyDevices = devices.filter((d) => d.busy).length;
|
|
95
|
+
const offlineDevices = devices.filter((d) => d.offline).length;
|
|
96
|
+
// Fetch persisted counters
|
|
97
|
+
const [sessionStarts, sessionSuccesses, sessionFailures, healingAttempts, healingSuccesses] = yield Promise.all([
|
|
98
|
+
this.getMetric('metric_session_starts'),
|
|
99
|
+
this.getMetric('metric_session_successes'),
|
|
100
|
+
this.getMetric('metric_session_failures'),
|
|
101
|
+
this.getMetric('metric_healing_attempts'),
|
|
102
|
+
this.getMetric('metric_healing_successes'),
|
|
103
|
+
]);
|
|
104
|
+
const lines = [
|
|
105
|
+
'# HELP xenon_sessions_total Total number of sessions across all time',
|
|
106
|
+
'# TYPE xenon_sessions_total counter',
|
|
107
|
+
`xenon_sessions_total{status="started"} ${sessionStarts}`,
|
|
108
|
+
`xenon_sessions_total{status="success"} ${sessionSuccesses}`,
|
|
109
|
+
`xenon_sessions_total{status="failure"} ${sessionFailures}`,
|
|
110
|
+
'# HELP xenon_sessions_active Current active sessions in the hub',
|
|
111
|
+
'# TYPE xenon_sessions_active gauge',
|
|
112
|
+
`xenon_sessions_active ${sessions.total}`,
|
|
113
|
+
'# HELP xenon_devices_total Total managed devices in the fleet',
|
|
114
|
+
'# TYPE xenon_devices_total gauge',
|
|
115
|
+
`xenon_devices_total ${totalDevices}`,
|
|
116
|
+
'# HELP xenon_devices_busy Current devices in active use',
|
|
117
|
+
'# TYPE xenon_devices_busy gauge',
|
|
118
|
+
`xenon_devices_busy ${busyDevices}`,
|
|
119
|
+
'# HELP xenon_devices_offline Devices currently not reachable',
|
|
120
|
+
'# TYPE xenon_devices_offline gauge',
|
|
121
|
+
`xenon_devices_offline ${offlineDevices}`,
|
|
122
|
+
'# HELP xenon_healing_total AI self-healing operations',
|
|
123
|
+
'# TYPE xenon_healing_total counter',
|
|
124
|
+
`xenon_healing_total{status="attempt"} ${healingAttempts}`,
|
|
125
|
+
`xenon_healing_total{status="success"} ${healingSuccesses}`,
|
|
126
|
+
];
|
|
127
|
+
return lines.join('\n') + '\n';
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
exports.MetricsService = MetricsService;
|
|
132
|
+
exports.MetricsService = MetricsService = __decorate([
|
|
133
|
+
(0, typedi_1.Service)()
|
|
134
|
+
], MetricsService);
|