@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.
Files changed (228) hide show
  1. package/README.md +446 -0
  2. package/lib/package.json +207 -0
  3. package/lib/public/assets/Layouts-7IT8aFLI.js +11 -0
  4. package/lib/public/assets/Layouts-DPMls9vh.css +1 -0
  5. package/lib/public/assets/ai-settings-BbnfgdEx.js +11 -0
  6. package/lib/public/assets/apps-CRMrI4_p.js +16 -0
  7. package/lib/public/assets/apps-CcM77dgg.css +1 -0
  8. package/lib/public/assets/badge-B1nKs8zj.css +1 -0
  9. package/lib/public/assets/badge-CSvl5xIU.js +11 -0
  10. package/lib/public/assets/button-CJlKn4PZ.css +1 -0
  11. package/lib/public/assets/button-CvLaGFYj.js +26 -0
  12. package/lib/public/assets/calendar-6w-D6Oaw.js +6 -0
  13. package/lib/public/assets/clock-DcdeWBPr.js +6 -0
  14. package/lib/public/assets/cpu-DiSoXT9n.js +6 -0
  15. package/lib/public/assets/device-explorer-CajM63OJ.js +193 -0
  16. package/lib/public/assets/device-explorer-CxdUAoTL.css +1 -0
  17. package/lib/public/assets/index-ByQwMN5T.js +174 -0
  18. package/lib/public/assets/index-C1DBaoSh.js +1 -0
  19. package/lib/public/assets/index-qzCez_kk.css +1 -0
  20. package/lib/public/assets/lock-B23ibZmo.js +6 -0
  21. package/lib/public/assets/maintenance-settings-CirzA6yG.js +6 -0
  22. package/lib/public/assets/mouse-pointer-2-Cz76SHFb.js +6 -0
  23. package/lib/public/assets/plus-BBwlIevt.js +6 -0
  24. package/lib/public/assets/session-dashboard-C2k7FFv_.css +1 -0
  25. package/lib/public/assets/session-dashboard-HPDtwPOZ.js +62 -0
  26. package/lib/public/assets/settings-DrZsZwdc.js +1 -0
  27. package/lib/public/assets/trash-2-DQpvzJec.js +6 -0
  28. package/lib/public/assets/useSocket-Dxsqae2a.js +16 -0
  29. package/lib/public/assets/webhook-settings-CDPgsgkb.css +1 -0
  30. package/lib/public/assets/webhook-settings-Cp-B4Nrw.js +1 -0
  31. package/lib/public/assets/zap-DovP6iow.js +6 -0
  32. package/lib/public/favicon.ico +0 -0
  33. package/lib/public/favicon.png +0 -0
  34. package/lib/public/favicon.svg +9 -0
  35. package/lib/public/index.html +46 -0
  36. package/lib/public/logo.svg +17 -0
  37. package/lib/public/logo192.png +0 -0
  38. package/lib/public/logo512.png +0 -0
  39. package/lib/public/manifest.json +25 -0
  40. package/lib/public/robots.txt +3 -0
  41. package/lib/schema.json +348 -0
  42. package/lib/src/InternalHttpClient.js +212 -0
  43. package/lib/src/PluginContext.js +29 -0
  44. package/lib/src/XenonCapabilityManager.js +199 -0
  45. package/lib/src/app/index.js +167 -0
  46. package/lib/src/app/routers/apps.js +79 -0
  47. package/lib/src/app/routers/config.js +131 -0
  48. package/lib/src/app/routers/control.js +835 -0
  49. package/lib/src/app/routers/dashboard.js +301 -0
  50. package/lib/src/app/routers/grid.js +352 -0
  51. package/lib/src/app/routers/reservation.js +190 -0
  52. package/lib/src/app/routers/webhook.js +83 -0
  53. package/lib/src/app/swagger-docs.js +203 -0
  54. package/lib/src/app/swagger.js +366 -0
  55. package/lib/src/chromeUtils.js +148 -0
  56. package/lib/src/commands/handle.js +19 -0
  57. package/lib/src/commands/index.js +8 -0
  58. package/lib/src/config.js +73 -0
  59. package/lib/src/dashboard/asset-manager.js +84 -0
  60. package/lib/src/dashboard/commands.js +284 -0
  61. package/lib/src/dashboard/event-manager.js +699 -0
  62. package/lib/src/dashboard/services/app-service.js +134 -0
  63. package/lib/src/dashboard/services/failure-analysis-service.js +173 -0
  64. package/lib/src/dashboard/services/session-service.js +113 -0
  65. package/lib/src/data-service/CircuitBreaker.js +83 -0
  66. package/lib/src/data-service/config-service.js +155 -0
  67. package/lib/src/data-service/db.js +122 -0
  68. package/lib/src/data-service/device-service.js +320 -0
  69. package/lib/src/data-service/device-store.interface.js +2 -0
  70. package/lib/src/data-service/device-store.js +345 -0
  71. package/lib/src/data-service/pending-sessions-service.js +25 -0
  72. package/lib/src/data-service/pluginArgs.js +25 -0
  73. package/lib/src/data-service/prisma-service.js +31 -0
  74. package/lib/src/data-service/prisma-store.js +385 -0
  75. package/lib/src/data-service/queue-service.js +150 -0
  76. package/lib/src/data-service/web-config-service.js +130 -0
  77. package/lib/src/device-managers/AndroidDeviceManager.js +1155 -0
  78. package/lib/src/device-managers/ChromeDriverManager.js +68 -0
  79. package/lib/src/device-managers/HealthMonitorService.js +325 -0
  80. package/lib/src/device-managers/IOSDeviceManager.js +351 -0
  81. package/lib/src/device-managers/NodeDevices.js +82 -0
  82. package/lib/src/device-managers/android/AndroidStreamService.js +370 -0
  83. package/lib/src/device-managers/android/DeviceLockManager.js +45 -0
  84. package/lib/src/device-managers/cloud/CapabilityManager.js +26 -0
  85. package/lib/src/device-managers/cloud/Devices.js +86 -0
  86. package/lib/src/device-managers/iOSTracker.js +44 -0
  87. package/lib/src/device-managers/index.js +89 -0
  88. package/lib/src/device-managers/ios/IOSDiscoveryService.js +268 -0
  89. package/lib/src/device-managers/ios/IOSStreamService.js +893 -0
  90. package/lib/src/device-managers/ios/WDAClient.js +866 -0
  91. package/lib/src/device-utils.js +663 -0
  92. package/lib/src/enums/Capabilities.js +8 -0
  93. package/lib/src/enums/Cloud.js +11 -0
  94. package/lib/src/enums/Platform.js +9 -0
  95. package/lib/src/enums/SessionType.js +9 -0
  96. package/lib/src/enums/SocketEvents.js +15 -0
  97. package/lib/src/helpers/UniversalMjpegProxy.js +273 -0
  98. package/lib/src/helpers/index.js +229 -0
  99. package/lib/src/index.js +95 -0
  100. package/lib/src/interceptors/CommandInterceptor.js +524 -0
  101. package/lib/src/interfaces/ICloudManager.js +2 -0
  102. package/lib/src/interfaces/IDevice.js +2 -0
  103. package/lib/src/interfaces/IDeviceFilterOptions.js +2 -0
  104. package/lib/src/interfaces/IDeviceManager.js +2 -0
  105. package/lib/src/interfaces/IOptions.js +2 -0
  106. package/lib/src/interfaces/IPluginArgs.js +55 -0
  107. package/lib/src/interfaces/ISessionCapability.js +2 -0
  108. package/lib/src/logger.js +225 -0
  109. package/lib/src/plugin.js +244 -0
  110. package/lib/src/prisma.js +12 -0
  111. package/lib/src/profiling/AndroidAppProfiler.js +213 -0
  112. package/lib/src/proxy/wd-command-proxy.js +221 -0
  113. package/lib/src/scripts/generate-database-migration.js +59 -0
  114. package/lib/src/scripts/initialize-database.js +55 -0
  115. package/lib/src/scripts/install-go-ios.js +66 -0
  116. package/lib/src/scripts/prepare-prisma.js +89 -0
  117. package/lib/src/services/AICommandService.js +143 -0
  118. package/lib/src/services/AIService.js +466 -0
  119. package/lib/src/services/CleanupService.js +141 -0
  120. package/lib/src/services/EventBus.js +74 -0
  121. package/lib/src/services/InspectorService.js +395 -0
  122. package/lib/src/services/MetricsService.js +134 -0
  123. package/lib/src/services/NetworkConditioningService.js +173 -0
  124. package/lib/src/services/NotificationService.js +163 -0
  125. package/lib/src/services/RequestLogService.js +252 -0
  126. package/lib/src/services/ResourceIsolationService.js +122 -0
  127. package/lib/src/services/SecurityService.js +120 -0
  128. package/lib/src/services/ServerManager.js +284 -0
  129. package/lib/src/services/SessionHeartbeatService.js +158 -0
  130. package/lib/src/services/SessionLifecycleService.js +572 -0
  131. package/lib/src/services/SocketClient.js +71 -0
  132. package/lib/src/services/SocketServer.js +87 -0
  133. package/lib/src/services/TracingService.js +132 -0
  134. package/lib/src/services/VideoPipelineService.js +220 -0
  135. package/lib/src/services/healing/FuzzyXmlHealingProvider.js +333 -0
  136. package/lib/src/services/healing/HealEtalonService.js +98 -0
  137. package/lib/src/services/healing/HealedLocatorGenerator.js +132 -0
  138. package/lib/src/services/healing/HealingOrchestrator.js +165 -0
  139. package/lib/src/services/healing/LlmHealingProvider.js +77 -0
  140. package/lib/src/services/healing/OcrHealingProvider.js +119 -0
  141. package/lib/src/services/healing/ResilioTreeHealingProvider.js +100 -0
  142. package/lib/src/services/healing/VisualAiHealingProvider.js +90 -0
  143. package/lib/src/services/healing/types.js +12 -0
  144. package/lib/src/services/omni-vision/OmniVisionService.js +718 -0
  145. package/lib/src/services/omni-vision/VisionAssertionService.js +68 -0
  146. package/lib/src/sessions/CloudSession.js +42 -0
  147. package/lib/src/sessions/LocalSession.js +313 -0
  148. package/lib/src/sessions/RemoteSession.js +287 -0
  149. package/lib/src/sessions/SessionManager.js +238 -0
  150. package/lib/src/sessions/XenonSession.js +44 -0
  151. package/lib/src/types/CLIArgs.js +2 -0
  152. package/lib/src/types/CloudArgs.js +2 -0
  153. package/lib/src/types/CloudSchema.js +131 -0
  154. package/lib/src/types/DeviceType.js +2 -0
  155. package/lib/src/types/DeviceUpdate.js +2 -0
  156. package/lib/src/types/IOSDevice.js +2 -0
  157. package/lib/src/types/Platform.js +2 -0
  158. package/lib/src/types/SessionStatus.js +11 -0
  159. package/lib/src/validators/CapabilityValidator.js +93 -0
  160. package/lib/test/e2e/android/conf.spec.js +43 -0
  161. package/lib/test/e2e/android/conf2.spec.js +44 -0
  162. package/lib/test/e2e/android/conf3.spec.js +44 -0
  163. package/lib/test/e2e/e2ehelper.js +113 -0
  164. package/lib/test/e2e/hubnode/forward-request.spec.js +224 -0
  165. package/lib/test/e2e/hubnode/hubnode.spec.js +214 -0
  166. package/lib/test/e2e/ios/conf1.spec.js +39 -0
  167. package/lib/test/e2e/ios/conf2.spec.js +39 -0
  168. package/lib/test/e2e/plugin-harness.js +236 -0
  169. package/lib/test/e2e/plugin.spec.js +97 -0
  170. package/lib/test/e2e/telemetry_verification.spec.js +83 -0
  171. package/lib/test/e2e/video-recording-test.spec.js +63 -0
  172. package/lib/test/helpers/test-container.js +112 -0
  173. package/lib/test/integration/androidDevices.spec.js +137 -0
  174. package/lib/test/integration/cliArgs.js +73 -0
  175. package/lib/test/integration/ios/01iOSSimulator.spec.js +291 -0
  176. package/lib/test/integration/ios/02iOSDevices.spec.js +75 -0
  177. package/lib/test/integration/testHelpers.js +74 -0
  178. package/lib/test/unit/AndroidDeviceManager.spec.js +178 -0
  179. package/lib/test/unit/ChromeDriverManager.spec.js +26 -0
  180. package/lib/test/unit/CleanupService.spec.js +21 -0
  181. package/lib/test/unit/DeviceModel.spec.js +157 -0
  182. package/lib/test/unit/FuzzyXmlHealingProvider.test.js +294 -0
  183. package/lib/test/unit/GetAdbOriginal.js +42 -0
  184. package/lib/test/unit/HealingCascade.test.js +128 -0
  185. package/lib/test/unit/IOSDeviceManager.spec.js +261 -0
  186. package/lib/test/unit/RemoteIOs.spec.js +78 -0
  187. package/lib/test/unit/ResilioTreeHealingProvider.test.js +96 -0
  188. package/lib/test/unit/commands.spec.js +27 -0
  189. package/lib/test/unit/config.spec.js +27 -0
  190. package/lib/test/unit/device-service.spec.js +307 -0
  191. package/lib/test/unit/device-utils.spec.js +313 -0
  192. package/lib/test/unit/fixtures/device.config.js +4 -0
  193. package/lib/test/unit/fixtures/devices.js +89 -0
  194. package/lib/test/unit/helpers.spec.js +62 -0
  195. package/lib/test/unit/omni-vision.spec.js +100 -0
  196. package/lib/test/unit/plugin.spec.js +133 -0
  197. package/lib/tsconfig.tsbuildinfo +1 -0
  198. package/package.json +207 -0
  199. package/prisma/data.db +0 -0
  200. package/prisma/dev.db +0 -0
  201. package/prisma/dev.db-journal +0 -0
  202. package/prisma/migrations/20231011074725_initial_tables/migration.sql +47 -0
  203. package/prisma/migrations/20231226115334_update_session_log/migration.sql +2 -0
  204. package/prisma/migrations/20251204113710_add_video_recording_enabled/migration.sql +29 -0
  205. package/prisma/migrations/20251204132449_add_log_table/migration.sql +11 -0
  206. package/prisma/migrations/20251205050111_add_profiling_support/migration.sql +47 -0
  207. package/prisma/migrations/20251205050947_add_is_error_field/migration.sql +24 -0
  208. package/prisma/migrations/20260126201337_add_app_model/migration.sql +18 -0
  209. package/prisma/migrations/20260130115722_add_performance_trace_and_xenon_sync/migration.sql +2 -0
  210. package/prisma/migrations/20260130135114_add_device_models/migration.sql +57 -0
  211. package/prisma/migrations/20260130140655_make_systemport_optional/migration.sql +45 -0
  212. package/prisma/migrations/20260130140932_make_device_fields_optional/migration.sql +45 -0
  213. package/prisma/migrations/20260130141040_final_schema_fix/migration.sql +45 -0
  214. package/prisma/migrations/20260130143234_add_device_health_fields/migration.sql +4 -0
  215. package/prisma/migrations/20260130144921_add_failure_category/migration.sql +2 -0
  216. package/prisma/migrations/20260131151456_add_webhook_config/migration.sql +10 -0
  217. package/prisma/migrations/20260201094507_add_device_tags/migration.sql +11 -0
  218. package/prisma/migrations/20260201103410_add_managed_process/migration.sql +15 -0
  219. package/prisma/migrations/20260201140637_add_web_config/migration.sql +22 -0
  220. package/prisma/migrations/20260201162232_add_session_progress/migration.sql +2 -0
  221. package/prisma/migrations/20260201174231_add_total_healed_count/migration.sql +2 -0
  222. package/prisma/migrations/migration_lock.toml +3 -0
  223. package/prisma/schema.prisma +210 -0
  224. package/schema.json +348 -0
  225. package/scripts/build-xenon.sh +32 -0
  226. package/scripts/dev/debug-gemini.ts +44 -0
  227. package/scripts/generate-types-from-schema.js +86 -0
  228. package/scripts/install-compatible-driver.js +39 -0
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ 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;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __metadata = (this && this.__metadata) || function (k, v) {
42
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
+ };
44
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
45
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
46
+ return new (P || (P = Promise))(function (resolve, reject) {
47
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
48
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
49
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
50
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
51
+ });
52
+ };
53
+ var __importDefault = (this && this.__importDefault) || function (mod) {
54
+ return (mod && mod.__esModule) ? mod : { "default": mod };
55
+ };
56
+ Object.defineProperty(exports, "__esModule", { value: true });
57
+ exports.HealingOrchestrator = void 0;
58
+ const typedi_1 = require("typedi");
59
+ const logger_1 = __importDefault(require("../../logger"));
60
+ const FuzzyXmlHealingProvider_1 = require("./FuzzyXmlHealingProvider");
61
+ const OcrHealingProvider_1 = require("./OcrHealingProvider");
62
+ const VisualAiHealingProvider_1 = require("./VisualAiHealingProvider");
63
+ const LlmHealingProvider_1 = require("./LlmHealingProvider");
64
+ const HealEtalonService_1 = require("./HealEtalonService");
65
+ const ResilioTreeHealingProvider_1 = require("./ResilioTreeHealingProvider");
66
+ let HealingOrchestrator = class HealingOrchestrator {
67
+ constructor(etalonService) {
68
+ this.etalonService = etalonService;
69
+ this.logger = logger_1.default.scope('HealingOrchestrator');
70
+ this.providers = [];
71
+ this.providers = [
72
+ new ResilioTreeHealingProvider_1.ResilioTreeHealingProvider(this.etalonService),
73
+ new FuzzyXmlHealingProvider_1.FuzzyXmlHealingProvider(this.etalonService),
74
+ new OcrHealingProvider_1.OcrHealingProvider(),
75
+ new VisualAiHealingProvider_1.VisualAiHealingProvider(),
76
+ new LlmHealingProvider_1.LlmHealingProvider(),
77
+ ];
78
+ }
79
+ attemptHealing(sessionId, driver, strategy, selector) {
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ this.logger.info(`🚨 Self-Healing triggered for session ${sessionId}. Broken locator: ${strategy}=${selector}`);
82
+ // Preparation: Collect data required for healing
83
+ // Note: We do this once to avoid multiple expensive round-trips
84
+ const context = { sessionId, driver, strategy, selector };
85
+ try {
86
+ this.logger.debug('Collecting page source and screenshot for analysis...');
87
+ const [xml, screenshot] = yield Promise.all([driver.getPageSource(), driver.getScreenshot()]);
88
+ context.pageSource = xml;
89
+ context.screenshotBase64 = screenshot;
90
+ }
91
+ catch (err) {
92
+ this.logger.error(`Failed to collect healing context: ${err.message}`);
93
+ return null;
94
+ }
95
+ // Tiered Execution: Try providers in order of cost/complexity
96
+ for (const provider of this.providers) {
97
+ try {
98
+ this.logger.info(`Attempting Tier ${provider.tier}: ${provider.name}...`);
99
+ const result = yield provider.heal(context);
100
+ if (result) {
101
+ this.logger.info(`✨ Provider ${provider.name} found a match! Confidence: ${(result.confidence * 100).toFixed(0)}%`);
102
+ // Tier 1/2 Optimization: Stability Verification Loop
103
+ // We try all candidates to see which one is the most stable (semantic vs absolute)
104
+ if (result.candidateSelectors && result.candidateSelectors.length > 0) {
105
+ this.logger.debug(`Verifying ${result.candidateSelectors.length} candidate locators for stability...`);
106
+ for (const candidate of result.candidateSelectors) {
107
+ try {
108
+ const elements = yield context.driver.findElements('xpath', candidate);
109
+ if (elements.length === 1) {
110
+ this.logger.info(`🎯 Verified stable & unique locator: ${candidate}`);
111
+ result.recommendedSelector = candidate;
112
+ break; // Found a unique stable locator
113
+ }
114
+ else if (elements.length > 1) {
115
+ this.logger.debug(`⚠️ Candidate locator is not unique (${elements.length} matches): ${candidate}`);
116
+ }
117
+ }
118
+ catch (e) {
119
+ this.logger.debug(`Candidate locator check failed: ${candidate}`);
120
+ }
121
+ }
122
+ }
123
+ // Principal Learning: Autonomously update the etalon to prevent future failures
124
+ // We only do this if confidence is over 70% to avoid pollution with false positives
125
+ if (result.confidence > 0.7 && result.node) {
126
+ try {
127
+ this.logger.info(`🧠 Learning from healing success: updating etalon for ${selector}`);
128
+ // Recalculate path for ResilioTree if node is available
129
+ let learnedPath = null;
130
+ if (result.node) {
131
+ try {
132
+ const { Path } = yield Promise.resolve().then(() => __importStar(require('resiliotree')));
133
+ const pathNodes = [];
134
+ let curr = result.node;
135
+ while (curr) {
136
+ pathNodes.unshift(curr);
137
+ curr = curr.parent || curr.parentNode;
138
+ }
139
+ learnedPath = new Path(pathNodes).toJSON();
140
+ }
141
+ catch (e) { }
142
+ }
143
+ yield this.etalonService.saveSignature(strategy, selector, result.node, learnedPath);
144
+ }
145
+ catch (learnErr) {
146
+ this.logger.debug(`Failed to update etalon after healing: ${learnErr.message}`);
147
+ }
148
+ }
149
+ return result;
150
+ }
151
+ }
152
+ catch (err) {
153
+ this.logger.error(`Provider ${provider.name} failed: ${err.message}`);
154
+ }
155
+ }
156
+ this.logger.warn(`❌ All healing tiers failed for selector: ${selector}`);
157
+ return null;
158
+ });
159
+ }
160
+ };
161
+ exports.HealingOrchestrator = HealingOrchestrator;
162
+ exports.HealingOrchestrator = HealingOrchestrator = __decorate([
163
+ (0, typedi_1.Service)(),
164
+ __metadata("design:paramtypes", [HealEtalonService_1.HealEtalonService])
165
+ ], HealingOrchestrator);
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.LlmHealingProvider = void 0;
16
+ const types_1 = require("./types");
17
+ const AIService_1 = require("../AIService");
18
+ const logger_1 = __importDefault(require("../../logger"));
19
+ class LlmHealingProvider {
20
+ constructor() {
21
+ this.name = 'LLM Reasoning Provider';
22
+ this.tier = types_1.HealingTier.TIER_5_LLM_REASONING;
23
+ this.logger = logger_1.default.scope('LlmHealing');
24
+ }
25
+ heal(context) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ var _a, _b;
28
+ if (!context.pageSource) {
29
+ this.logger.debug('No page source available for LLM reasoning');
30
+ return null;
31
+ }
32
+ // Check if AI service is enabled before attempting
33
+ if (!AIService_1.AI_SERVICE.isEnabled()) {
34
+ this.logger.debug('AI service not enabled, skipping LLM healing');
35
+ return null;
36
+ }
37
+ try {
38
+ this.logger.info(`Attempting Deep LLM healing for locator: "${context.selector}"`);
39
+ const healingResult = yield AIService_1.AI_SERVICE.healLocator({
40
+ selector: context.selector,
41
+ strategy: context.strategy,
42
+ xml: context.pageSource,
43
+ screenshotBase64: context.screenshotBase64,
44
+ });
45
+ if (healingResult && healingResult.recommendedXpath) {
46
+ this.logger.info(`✅ LLM found healing path: ${healingResult.recommendedXpath}`);
47
+ // Try to resolve the element to ensure it exists
48
+ try {
49
+ const element = yield context.driver.findElement('xpath', healingResult.recommendedXpath);
50
+ return {
51
+ id: element.ELEMENT || element['element-6066-11e4-a52e-4f735466cecf'],
52
+ tier: this.tier,
53
+ confidence: 0.95, // Deep reasoning is typically highly accurate
54
+ originalSelector: context.selector,
55
+ recommendedSelector: healingResult.recommendedXpath,
56
+ message: `Found alternative element via Deep AI Reasoning: ${healingResult.reason}`,
57
+ };
58
+ }
59
+ catch (err) {
60
+ this.logger.error('LLM recommended path failed to resolve on device');
61
+ }
62
+ }
63
+ }
64
+ catch (err) {
65
+ // Log service unavailability at debug level (expected), other errors at warn level
66
+ if (((_a = err.message) === null || _a === void 0 ? void 0 : _a.includes('unavailable')) || ((_b = err.message) === null || _b === void 0 ? void 0 : _b.includes('404'))) {
67
+ this.logger.debug(`LLM healing skipped: ${err.message}`);
68
+ }
69
+ else {
70
+ this.logger.warn(`Error during LLM healing: ${err.message}`);
71
+ }
72
+ }
73
+ return null;
74
+ });
75
+ }
76
+ }
77
+ exports.LlmHealingProvider = LlmHealingProvider;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.OcrHealingProvider = void 0;
16
+ const types_1 = require("./types");
17
+ const tesseract_js_1 = __importDefault(require("tesseract.js"));
18
+ const logger_1 = __importDefault(require("../../logger"));
19
+ class OcrHealingProvider {
20
+ constructor() {
21
+ this.name = 'OCR Text Provider';
22
+ this.tier = types_1.HealingTier.TIER_3_LOCAL_OCR;
23
+ this.logger = logger_1.default.scope('OcrHealing');
24
+ }
25
+ heal(context) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ if (!context.screenshotBase64) {
28
+ this.logger.debug('No screenshot available for OCR matching');
29
+ return null;
30
+ }
31
+ try {
32
+ // Extract what text we are looking for from the selector
33
+ const soughtText = this.extractTextHint(context.selector);
34
+ if (!soughtText)
35
+ return null;
36
+ this.logger.info(`Attempting OCR search for text: "${soughtText}"`);
37
+ // Run OCR on the screenshot
38
+ const buffer = Buffer.from(context.screenshotBase64, 'base64');
39
+ const result = yield tesseract_js_1.default.recognize(buffer, 'eng');
40
+ const { words } = result.data;
41
+ // Look for the best word match
42
+ const bestWord = words
43
+ ? words.find((w) => w.text.toLowerCase().includes(soughtText.toLowerCase()) ||
44
+ soughtText.toLowerCase().includes(w.text.toLowerCase()))
45
+ : null;
46
+ if (bestWord) {
47
+ this.logger.info(`✅ OCR found text "${bestWord.text}" at ${JSON.stringify(bestWord.bbox)}`);
48
+ // Convert bbox to center coordinates
49
+ const x = Math.round((bestWord.bbox.x0 + bestWord.bbox.x1) / 2);
50
+ const y = Math.round((bestWord.bbox.y0 + bestWord.bbox.y1) / 2);
51
+ // Try to get the REAL element at these coordinates by tapping
52
+ try {
53
+ // Use W3C Actions to tap at the OCR-detected coordinates, then find the element at that position
54
+ const element = yield context.driver
55
+ .findElement('-ios predicate string', `label CONTAINS[c] "${bestWord.text.replace(/"/g, '\\"')}"`)
56
+ .catch(() => null);
57
+ if (element) {
58
+ const elementId = element.ELEMENT || element['element-6066-11e4-a52e-4f735466cecf'];
59
+ if (elementId) {
60
+ this.logger.info('🎯 OCR resolved to real element via predicate search');
61
+ return {
62
+ id: elementId,
63
+ tier: this.tier,
64
+ confidence: bestWord.confidence / 100,
65
+ originalSelector: context.selector,
66
+ recommendedSelector: `ocr:text="${bestWord.text}"`,
67
+ message: `Found text "${bestWord.text}" via local OCR (${bestWord.confidence.toFixed(0)}% confidence)`,
68
+ rect: {
69
+ x: bestWord.bbox.x0,
70
+ y: bestWord.bbox.y0,
71
+ width: bestWord.bbox.x1 - bestWord.bbox.x0,
72
+ height: bestWord.bbox.y1 - bestWord.bbox.y0,
73
+ },
74
+ };
75
+ }
76
+ }
77
+ }
78
+ catch (predErr) {
79
+ this.logger.debug(`Predicate search failed: ${predErr.message}`);
80
+ }
81
+ // Fallback: Return coordinate-based result with virtual ID
82
+ // The CommandInterceptor handles rect-based results via coordinate tap
83
+ return {
84
+ id: `healed_ocr_${Date.now()}`,
85
+ tier: this.tier,
86
+ confidence: bestWord.confidence / 100,
87
+ originalSelector: context.selector,
88
+ recommendedSelector: `ocr:text="${bestWord.text}"`,
89
+ message: `Found text "${bestWord.text}" via local OCR (${bestWord.confidence.toFixed(0)}% confidence)`,
90
+ rect: {
91
+ x: bestWord.bbox.x0,
92
+ y: bestWord.bbox.y0,
93
+ width: bestWord.bbox.x1 - bestWord.bbox.x0,
94
+ height: bestWord.bbox.y1 - bestWord.bbox.y0,
95
+ },
96
+ };
97
+ }
98
+ }
99
+ catch (err) {
100
+ this.logger.error(`Error during OCR healing: ${err.message}`);
101
+ }
102
+ return null;
103
+ });
104
+ }
105
+ extractTextHint(selector) {
106
+ // If selector is //*[@text='Login'] or similar, grab 'Login'
107
+ const textMatch = selector.match(/text=['"]([^'"]+)['"]/i) ||
108
+ selector.match(/content-desc=['"]([^'"]+)['"]/i) ||
109
+ selector.match(/label=['"]([^'"]+)['"]/i) ||
110
+ selector.match(/name=['"]([^'"]+)['"]/i);
111
+ if (textMatch)
112
+ return textMatch[1];
113
+ // Otherwise try to grab last part of selector if it looks like words
114
+ const parts = selector.split(/[\/\@\[\]\=\'\"]/);
115
+ const lastWord = parts.reverse().find((p) => p.length > 3);
116
+ return lastWord || null;
117
+ }
118
+ }
119
+ exports.OcrHealingProvider = OcrHealingProvider;
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ResilioTreeHealingProvider = void 0;
16
+ const types_1 = require("./types");
17
+ const { Path, PathFinder, JSDOMParser, LCSPathDistance, HeuristicNodeDistance, } = require('resiliotree');
18
+ const typedi_1 = require("typedi");
19
+ const logger_1 = __importDefault(require("../../logger"));
20
+ const HealedLocatorGenerator_1 = require("./HealedLocatorGenerator");
21
+ class ResilioTreeHealingProvider {
22
+ constructor(etalonService) {
23
+ this.etalonService = etalonService;
24
+ this.name = 'ResilioTree Provider';
25
+ this.tier = types_1.HealingTier.TIER_1_RECOVERY; // High priority robust recovery
26
+ this.logger = logger_1.default.scope('ResilioTreeHealing');
27
+ this.generator = typedi_1.Container.get(HealedLocatorGenerator_1.HealedLocatorGenerator);
28
+ }
29
+ heal(context) {
30
+ return __awaiter(this, void 0, void 0, function* () {
31
+ if (!context.pageSource) {
32
+ this.logger.debug('No page source available for ResilioTree healing');
33
+ return null;
34
+ }
35
+ try {
36
+ const signature = yield this.etalonService.getSignature(context.selector);
37
+ if (!signature || !signature.path) {
38
+ this.logger.debug(`No ResilioTree path found for selector: ${context.selector}`);
39
+ return null;
40
+ }
41
+ this.logger.info(`Attempting robust ResilioTree recovery for: ${context.selector}`);
42
+ // 1. Parse current page source into ResilioTree model
43
+ const parser = new JSDOMParser();
44
+ const targetRoot = parser.parse(context.pageSource);
45
+ // 2. Revive the saved path from JSON
46
+ const savedPath = Path.fromJSON(signature.path);
47
+ // 3. Find the nearest node using ResilioTree's PathFinder
48
+ const pathDistance = new LCSPathDistance();
49
+ const nodeDistance = new HeuristicNodeDistance();
50
+ const pathFinder = new PathFinder(pathDistance, nodeDistance);
51
+ const nearestNode = pathFinder.findNearest(savedPath, targetRoot);
52
+ if (nearestNode) {
53
+ const candidateLocators = this.generator.generate(nearestNode);
54
+ const recommendedXpath = candidateLocators[0] || this.generateXpath(nearestNode);
55
+ this.logger.info(`ResilioTree suggested recovery XPath: ${recommendedXpath}`);
56
+ try {
57
+ const healedElement = yield context.driver.findElement('xpath', recommendedXpath);
58
+ if (healedElement) {
59
+ return {
60
+ id: healedElement.ELEMENT || healedElement['element-6066-11e4-a52e-4f735466cecf'],
61
+ originalSelector: context.selector,
62
+ recommendedSelector: recommendedXpath,
63
+ candidateSelectors: candidateLocators,
64
+ confidence: 0.9, // ResilioTree path matching is high confidence
65
+ tier: this.tier,
66
+ node: nearestNode,
67
+ message: `Recovered via ResilioTree path matching. New XPath: ${recommendedXpath}`,
68
+ };
69
+ }
70
+ }
71
+ catch (e) {
72
+ this.logger.debug(`Driver failed to find element suggested by ResilioTree: ${recommendedXpath}`);
73
+ }
74
+ }
75
+ }
76
+ catch (err) {
77
+ this.logger.error(`Error during ResilioTree healing: ${err.message}`);
78
+ }
79
+ return null;
80
+ });
81
+ }
82
+ /**
83
+ * Generates an absolute XPath for a ResilioTree Node
84
+ */
85
+ generateXpath(node) {
86
+ const parts = [];
87
+ let curr = node;
88
+ while (curr) {
89
+ const tag = curr.tag.toLowerCase();
90
+ // Skip JSDOM added wrapper tags for mobile compatibility
91
+ if (tag !== 'html' && tag !== 'body') {
92
+ const index = curr.index + 1; // XPath is 1-based
93
+ parts.unshift(`${curr.tag}[${index}]`);
94
+ }
95
+ curr = curr.parent;
96
+ }
97
+ return `/${parts.join('/')}`;
98
+ }
99
+ }
100
+ exports.ResilioTreeHealingProvider = ResilioTreeHealingProvider;
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.VisualAiHealingProvider = void 0;
16
+ const types_1 = require("./types");
17
+ const AIService_1 = require("../AIService");
18
+ const logger_1 = __importDefault(require("../../logger"));
19
+ class VisualAiHealingProvider {
20
+ constructor() {
21
+ this.name = 'Visual AI Provider';
22
+ this.tier = types_1.HealingTier.TIER_4_VISUAL_AI;
23
+ this.logger = logger_1.default.scope('VisualAiHealing');
24
+ }
25
+ heal(context) {
26
+ return __awaiter(this, void 0, void 0, function* () {
27
+ var _a, _b;
28
+ if (!context.screenshotBase64) {
29
+ this.logger.debug('No screenshot available for visual AI matching');
30
+ return null;
31
+ }
32
+ // Check if AI service is enabled before attempting
33
+ if (!AIService_1.AI_SERVICE.isEnabled()) {
34
+ this.logger.debug('AI service not enabled, skipping Visual AI healing');
35
+ return null;
36
+ }
37
+ try {
38
+ // Describe what we are looking for based on the selector
39
+ const description = this.generateVisualDescription(context.selector);
40
+ this.logger.info(`Attempting Visual AI find for: "${description}"`);
41
+ const coordinates = yield AIService_1.AI_SERVICE.visualFind(context.screenshotBase64, description);
42
+ if (coordinates && typeof coordinates.x === 'number' && typeof coordinates.y === 'number') {
43
+ this.logger.info(`✅ Visual AI found element at (${coordinates.x}, ${coordinates.y})`);
44
+ return {
45
+ id: `healed_visual_${Date.now()}`,
46
+ tier: this.tier,
47
+ confidence: 0.8, // Basic vision models don't always give confidence, assuming high if found
48
+ originalSelector: context.selector,
49
+ recommendedSelector: `visual:description="${description}"`,
50
+ message: `Found element visually via AI Vision (${description})`,
51
+ rect: {
52
+ x: coordinates.x - 20, // Approximate bounding box
53
+ y: coordinates.y - 20,
54
+ width: 40,
55
+ height: 40,
56
+ },
57
+ };
58
+ }
59
+ }
60
+ catch (err) {
61
+ // Log service unavailability at debug level (expected), other errors at warn level
62
+ if (((_a = err.message) === null || _a === void 0 ? void 0 : _a.includes('unavailable')) || ((_b = err.message) === null || _b === void 0 ? void 0 : _b.includes('404'))) {
63
+ this.logger.debug(`Visual AI healing skipped: ${err.message}`);
64
+ }
65
+ else {
66
+ this.logger.warn(`Error during Visual AI healing: ${err.message}`);
67
+ }
68
+ }
69
+ return null;
70
+ });
71
+ }
72
+ generateVisualDescription(selector) {
73
+ // Convert selector to a descriptive string for the AI
74
+ // Handle iOS (@name, @label) and Android (@text, @content-desc) patterns
75
+ const textMatch = selector.match(/text=['"]([^'"]+)['"]/i) ||
76
+ selector.match(/label=['"]([^'"]+)['"]/i) ||
77
+ selector.match(/name=['"]([^'"]+)['"]/i) ||
78
+ selector.match(/content-desc=['"]([^'"]+)['"]/i);
79
+ if (textMatch) {
80
+ return `the element with text "${textMatch[1]}"`;
81
+ }
82
+ // Clean up IDs
83
+ const idMatch = selector.match(/id=['"]([^'"]+)['"]/i) || selector.match(/resource-id=['"]([^'"]+)['"]/i);
84
+ if (idMatch) {
85
+ return `the element with ID or identifier "${idMatch[1]}"`;
86
+ }
87
+ return `the element described by the locator "${selector}"`;
88
+ }
89
+ }
90
+ exports.VisualAiHealingProvider = VisualAiHealingProvider;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HealingTier = void 0;
4
+ var HealingTier;
5
+ (function (HealingTier) {
6
+ HealingTier[HealingTier["TIER_1_RECOVERY"] = 0] = "TIER_1_RECOVERY";
7
+ HealingTier[HealingTier["TIER_1_NATIVE"] = 1] = "TIER_1_NATIVE";
8
+ HealingTier[HealingTier["TIER_2_FUZZY_XML"] = 2] = "TIER_2_FUZZY_XML";
9
+ HealingTier[HealingTier["TIER_3_LOCAL_OCR"] = 3] = "TIER_3_LOCAL_OCR";
10
+ HealingTier[HealingTier["TIER_4_VISUAL_AI"] = 4] = "TIER_4_VISUAL_AI";
11
+ HealingTier[HealingTier["TIER_5_LLM_REASONING"] = 5] = "TIER_5_LLM_REASONING";
12
+ })(HealingTier || (exports.HealingTier = HealingTier = {}));