@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,236 @@
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.pluginE2EHarness = pluginE2EHarness;
16
+ const support_1 = require("appium/support");
17
+ const get_port_1 = __importDefault(require("get-port"));
18
+ const log_symbols_1 = require("log-symbols");
19
+ const teen_process_1 = require("teen_process");
20
+ const path_1 = __importDefault(require("path"));
21
+ const js_yaml_1 = __importDefault(require("js-yaml"));
22
+ const fs_1 = __importDefault(require("fs"));
23
+ const ip_1 = __importDefault(require("ip"));
24
+ const axios_1 = __importDefault(require("axios"));
25
+ /**
26
+ * Creates hooks to install a driver and a plugin and starts an Appium server w/ the given extensions.
27
+ * @param {E2ESetupOpts} opts
28
+ * @returns {void}
29
+ */
30
+ function pluginE2EHarness(opts) {
31
+ let { port } = opts;
32
+ const { appiumHome, before, after, configFile, driverSource, driverPackage, driverName, driverSpec, pluginSource, pluginPackage, pluginSpec, pluginName, host, enableGoIos, } = opts;
33
+ let server;
34
+ function goIosPath() {
35
+ return __awaiter(this, void 0, void 0, function* () {
36
+ const appium_path = path_1.default.dirname(require.resolve('appium'));
37
+ console.log(`${log_symbols_1.info} appium_path: ${appium_path}`);
38
+ const node_modules_root = (yield (0, teen_process_1.exec)('npm', ['root', '-g'])).stdout.trim();
39
+ console.log(`${log_symbols_1.info} node_modules_root: ${node_modules_root}`);
40
+ const platform_name = process.platform;
41
+ const arch_name = process.arch;
42
+ const go_ios_dir = path_1.default.join(node_modules_root, 'go-ios');
43
+ // find ios binary matching platform name
44
+ let go_ios_bin = fs_1.default.readdirSync(go_ios_dir, { recursive: true }).find((item) => {
45
+ console.log(`${log_symbols_1.info} item: ${item}`);
46
+ return item.includes(platform_name);
47
+ });
48
+ console.log(`${log_symbols_1.info} platform: ${platform_name} arch: ${arch_name} go_ios_bin: ${go_ios_bin}`);
49
+ if (!go_ios_bin) {
50
+ // throw new Error(`go-ios binary not found for platform ${platform_name}`);
51
+ go_ios_bin = '';
52
+ console.log(`${log_symbols_1.warning} go-ios binary not found for platform ${platform_name}`);
53
+ }
54
+ const full_path = path_1.default.join(go_ios_dir, go_ios_bin.toString(), 'ios');
55
+ return full_path;
56
+ });
57
+ }
58
+ // return appium binary path based on APPIUM_HOME
59
+ function getAppiumBin() {
60
+ return require.resolve('appium');
61
+ }
62
+ function startPlugin() {
63
+ return __awaiter(this, void 0, void 0, function* () {
64
+ const setupAppiumHome = () => __awaiter(this, void 0, void 0, function* () {
65
+ /**
66
+ * @type {AppiumEnv}
67
+ */
68
+ const env = Object.assign({}, process.env);
69
+ if (appiumHome) {
70
+ env.APPIUM_HOME = appiumHome;
71
+ //env.HOME = appiumHome;
72
+ yield support_1.fs.mkdirp(appiumHome);
73
+ console.log(`${log_symbols_1.info} Set \`APPIUM_HOME\` to ${appiumHome}`);
74
+ }
75
+ // find go_ios from npm
76
+ if (enableGoIos)
77
+ env.GO_IOS = yield goIosPath();
78
+ return env;
79
+ });
80
+ /**
81
+ *
82
+ * @param {AppiumEnv} env
83
+ */
84
+ const installDriver = (env) => __awaiter(this, void 0, void 0, function* () {
85
+ var _a;
86
+ const APPIUM_BIN = getAppiumBin();
87
+ console.log(`${log_symbols_1.info} Checking if driver "${driverName}" is installed...`);
88
+ const driverListArgs = [APPIUM_BIN, 'driver', 'list', '--json'];
89
+ console.log(`${log_symbols_1.info} Running: ${process.execPath} ${driverListArgs.join(' ')}`);
90
+ const { stdout: driverListJson } = yield (0, teen_process_1.exec)(process.execPath, driverListArgs, {
91
+ env,
92
+ });
93
+ const installedDrivers = JSON.parse(driverListJson);
94
+ if (!((_a = installedDrivers[driverName]) === null || _a === void 0 ? void 0 : _a.installed)) {
95
+ console.log(`${log_symbols_1.warning} Driver "${driverName}" not installed; installing...`);
96
+ const driverArgs = [APPIUM_BIN, 'driver', 'install', '--source', driverSource, driverSpec];
97
+ if (driverPackage) {
98
+ driverArgs.push('--package', driverPackage);
99
+ }
100
+ console.log(`${log_symbols_1.info} Running: ${process.execPath} ${driverArgs.join(' ')}`);
101
+ yield (0, teen_process_1.exec)(process.execPath, driverArgs, {
102
+ env,
103
+ });
104
+ }
105
+ console.log(`${log_symbols_1.success} Installed driver "${driverName}"`);
106
+ });
107
+ function removePluginFromExtensionsYaml(env) {
108
+ return __awaiter(this, void 0, void 0, function* () {
109
+ const extensionsYaml = path_1.default.join(env.APPIUM_HOME, 'node_modules', '.cache', 'appium', 'extensions.yaml');
110
+ console.log(`${log_symbols_1.info} Removing plugin "${pluginName}" from ${extensionsYaml}`);
111
+ const extensions = js_yaml_1.default.load(fs_1.default.readFileSync(extensionsYaml, 'utf8'));
112
+ delete extensions.plugins[pluginName];
113
+ console.log(`${log_symbols_1.info} Writing back to ${extensionsYaml}`);
114
+ fs_1.default.writeFileSync(extensionsYaml, js_yaml_1.default.dump(extensions));
115
+ });
116
+ }
117
+ /**
118
+ *
119
+ * @param {AppiumEnv} env
120
+ */
121
+ const installPlugin = (env) => __awaiter(this, void 0, void 0, function* () {
122
+ /*const availablePlugins = await installedPluginsByAppiumCommands(env);
123
+ console.log(`${info} Available plugins: ${JSON.stringify(Object.keys(availablePlugins), null, 2)}`);
124
+ const installedPlugins = Object.keys(availablePlugins).map((item) => availablePlugins[item]).filter((p: any) => p.installed);
125
+ console.log(`${info} Installed plugin: ${JSON.stringify(installedPlugins, null, 2)}`);
126
+ */
127
+ // same plugin maybe installed via different source: npm or local
128
+ // we don't care, just remove it and write it back to the file
129
+ yield removePluginFromExtensionsYaml(env);
130
+ // installing our version of plugin
131
+ const pluginArgs = [
132
+ getAppiumBin(),
133
+ 'plugin',
134
+ 'install',
135
+ '--source',
136
+ pluginSource,
137
+ pluginSpec,
138
+ ];
139
+ // only aplicable for npm
140
+ if (pluginPackage) {
141
+ pluginArgs.push('--package', pluginPackage);
142
+ }
143
+ console.log(`${log_symbols_1.info} Installing plugin: ${process.execPath} ${pluginArgs.join(' ')}`);
144
+ yield (0, teen_process_1.exec)(process.execPath, pluginArgs, { env });
145
+ console.log(`${log_symbols_1.success} Installed plugin "${pluginName}"`);
146
+ });
147
+ const createServer = () => __awaiter(this, void 0, void 0, function* () {
148
+ if (!port) {
149
+ port = yield (0, get_port_1.default)();
150
+ }
151
+ console.log(`${log_symbols_1.info} Will use port ${port} for Appium server`);
152
+ // here we are using CLI (instead of AppiumServer) to prevent schema conflicts
153
+ yield runAppiumServerFromCli(env, [pluginName], [driverName], configFile);
154
+ // use axios to wait until port is returning 200 OK
155
+ console.log(`${log_symbols_1.info} Waiting for Appium server to be ready...`);
156
+ });
157
+ function runAppiumServerFromCli(env_1) {
158
+ return __awaiter(this, arguments, void 0, function* (env, usePlugins = [], useDrivers = [], configFile = '') {
159
+ /**
160
+ example:
161
+ appium server -ka 800 \
162
+ --use-plugins=xenon,appium-dashboard \
163
+ --relaxed-security \
164
+ --allow-insecure chromedriver_autodownload,execute_driver_script,adb_shell \
165
+ --config ./hub-config.json \
166
+ -pa /wd/hub
167
+ */
168
+ const APPIUM_BIN = getAppiumBin();
169
+ const serverArgs = [APPIUM_BIN, 'server', '-ka', '800'];
170
+ if (usePlugins.length > 0) {
171
+ serverArgs.push(`--use-plugins=${usePlugins.join(',')}`);
172
+ }
173
+ if (useDrivers.length > 0) {
174
+ serverArgs.push(`--use-drivers=${useDrivers.join(',')}`);
175
+ }
176
+ if (configFile) {
177
+ serverArgs.push(`--config=${configFile}`);
178
+ }
179
+ console.log(`APPIUM_HOME=${env.APPIUM_HOME} GO_IOS=${env.GO_IOS}`);
180
+ console.log(`${log_symbols_1.info} Running: ${process.execPath} ${serverArgs.join(' ')}`);
181
+ (0, teen_process_1.exec)(process.execPath, serverArgs, {
182
+ env,
183
+ });
184
+ return waitServer(host !== null && host !== void 0 ? host : ip_1.default.address(), port !== null && port !== void 0 ? port : 4723, 60);
185
+ });
186
+ }
187
+ // Use axios to hit appium endpoint until it returns 200 OK
188
+ function waitServer(host, port, timeoutSeconds) {
189
+ return __awaiter(this, void 0, void 0, function* () {
190
+ // const basePath = serverArgs.basePath || '';
191
+ const url = `http://${host}:${port}/status`;
192
+ const timeout = timeoutSeconds * 1000;
193
+ const start = Date.now();
194
+ while (Date.now() - start < timeout) {
195
+ try {
196
+ yield axios_1.default.get(url);
197
+ return;
198
+ }
199
+ catch (ign) {
200
+ // ignore
201
+ console.log(`${log_symbols_1.info} url: ${url} error: ${ign.message}`);
202
+ }
203
+ yield new Promise((resolve) => setTimeout(resolve, 1000));
204
+ }
205
+ throw new Error(`Appium server did not start after ${timeoutSeconds} seconds`);
206
+ });
207
+ }
208
+ const env = yield setupAppiumHome();
209
+ yield installDriver(env);
210
+ yield installPlugin(env);
211
+ yield createServer();
212
+ });
213
+ }
214
+ function stopPlugin() {
215
+ return __awaiter(this, void 0, void 0, function* () {
216
+ if (server) {
217
+ yield server.close();
218
+ }
219
+ });
220
+ }
221
+ // clean it after test
222
+ after(stopPlugin);
223
+ // have an option to start the plugin before the test manually
224
+ // this is useful to start multiple plugins in a single test
225
+ if (before) {
226
+ console.log("Adding plugin startup into mocha's before hook");
227
+ before(startPlugin);
228
+ }
229
+ else {
230
+ console.log(`Please start plugin ${pluginName} manually using "startPlugin()" function`);
231
+ }
232
+ return {
233
+ startPlugin,
234
+ stopPlugin,
235
+ };
236
+ }
@@ -0,0 +1,97 @@
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
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
16
+ const NodeDevices_1 = __importDefault(require("../../src/device-managers/NodeDevices"));
17
+ const chai_1 = __importDefault(require("chai"));
18
+ chai_1.default.should();
19
+ const chai_2 = require("chai");
20
+ const axios_1 = __importDefault(require("axios"));
21
+ const e2ehelper_1 = require("./e2ehelper");
22
+ const plugin_test_support_1 = require("@appium/plugin-test-support");
23
+ const ip_1 = __importDefault(require("ip"));
24
+ const db_1 = require("../../src/data-service/db");
25
+ describe('Basic Plugin Test', () => {
26
+ // dump hub config into a file
27
+ const hub_config_file = (0, e2ehelper_1.ensureHubConfig)();
28
+ // dump node config into a file
29
+ (0, e2ehelper_1.ensureNodeConfig)();
30
+ // setup appium home
31
+ const APPIUM_HOME = (0, e2ehelper_1.ensureAppiumHome)();
32
+ // run hub
33
+ (0, plugin_test_support_1.pluginE2EHarness)({
34
+ before: global.before,
35
+ after: global.after,
36
+ serverArgs: {
37
+ subcommand: 'server',
38
+ configFile: hub_config_file,
39
+ },
40
+ pluginName: 'xenon',
41
+ port: e2ehelper_1.HUB_APPIUM_PORT,
42
+ driverSource: 'npm',
43
+ driverName: 'uiautomator2',
44
+ driverSpec: 'appium-uiautomator2-driver',
45
+ pluginSource: 'local',
46
+ pluginSpec: e2ehelper_1.PLUGIN_PATH,
47
+ appiumHome: APPIUM_HOME,
48
+ });
49
+ const hub_url = `http://${ip_1.default.address()}:${e2ehelper_1.HUB_APPIUM_PORT}`;
50
+ it('Basic Plugin test', () => __awaiter(void 0, void 0, void 0, function* () {
51
+ (yield axios_1.default.get(`${hub_url}/xenon`)).status.should.eql(200);
52
+ }));
53
+ it('Basic Plugin API test', () => __awaiter(void 0, void 0, void 0, function* () {
54
+ (yield axios_1.default.get(`${hub_url}/xenon/api/device`)).status.should.eql(200);
55
+ }));
56
+ it('Add Android devices from node to hub', () => __awaiter(void 0, void 0, void 0, function* () {
57
+ (yield db_1.XenonDatabase.DeviceModel).removeDataOnly();
58
+ const nodeAndroidDevice = [
59
+ {
60
+ adbRemoteHost: null,
61
+ adbPort: 5039,
62
+ systemPort: 58296,
63
+ sdk: '13',
64
+ realDevice: false,
65
+ name: 'sdk_gphone64_arm64',
66
+ busy: false,
67
+ state: 'device',
68
+ udid: 'emulator-5551',
69
+ platform: 'android',
70
+ deviceType: 'emulator',
71
+ host: 'http://127.2.1.41:4723',
72
+ totalUtilizationTimeMilliSec: 7023014,
73
+ sessionStartTime: 0,
74
+ },
75
+ ];
76
+ const nodeDevices = new NodeDevices_1.default(hub_url);
77
+ yield nodeDevices.postDevicesToHub(nodeAndroidDevice, 'add');
78
+ const devices = (yield axios_1.default.get(`${hub_url}/xenon/api/device`)).data;
79
+ devices.find((d) => d.udid === 'emulator-5551').should.to.be.an('object');
80
+ nodeAndroidDevice[0].udid = 'emulator-5552';
81
+ yield nodeDevices.postDevicesToHub(nodeAndroidDevice, 'add');
82
+ const updatedDeviceList = (yield axios_1.default.get(`${hub_url}/xenon/api/device`)).data;
83
+ //updatedDeviceList.should.have.lengthOf(2);
84
+ updatedDeviceList.find((d) => d.udid === 'emulator-5552').should.to.be.an('object');
85
+ }));
86
+ it('Remove Android devices from node to hub', () => __awaiter(void 0, void 0, void 0, function* () {
87
+ const nodeDevices = new NodeDevices_1.default(hub_url);
88
+ const devices = (yield axios_1.default.get(`${hub_url}/xenon/api/device`)).data;
89
+ const exptectedDevice = devices.find((d) => d.udid === 'emulator-5551');
90
+ devices.find((d) => d.udid === 'emulator-5551').should.to.be.an('object');
91
+ console.log('devices', exptectedDevice);
92
+ yield nodeDevices.postDevicesToHub([{ udid: 'emulator-5551', host: '127.2.1.41' }], 'remove');
93
+ const updatedDeviceList = (yield axios_1.default.get(`${hub_url}/xenon/api/device`)).data;
94
+ const find = updatedDeviceList.find((d) => d.udid === 'emulator-5551');
95
+ (0, chai_2.expect)(find).to.be.undefined;
96
+ }));
97
+ });
@@ -0,0 +1,83 @@
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
+ const chai_1 = __importDefault(require("chai"));
16
+ chai_1.default.should();
17
+ const chai_2 = require("chai");
18
+ const e2ehelper_1 = require("./e2ehelper");
19
+ const plugin_test_support_1 = require("@appium/plugin-test-support");
20
+ const ip_1 = __importDefault(require("ip"));
21
+ const webdriverio_1 = require("webdriverio");
22
+ const prisma_1 = require("../../src/prisma");
23
+ describe('Performance Telemetry Verification', () => {
24
+ const APPIUM_HOME = (0, e2ehelper_1.ensureAppiumHome)('telemetry-verify', true);
25
+ const hub_config_file = (0, e2ehelper_1.ensureHubConfig)('android', 'both', 'both', {
26
+ enableDashboard: true,
27
+ });
28
+ (0, plugin_test_support_1.pluginE2EHarness)({
29
+ before: global.before,
30
+ after: global.after,
31
+ serverArgs: {
32
+ subcommand: 'server',
33
+ configFile: hub_config_file,
34
+ },
35
+ pluginName: 'xenon',
36
+ port: e2ehelper_1.HUB_APPIUM_PORT,
37
+ driverSource: 'npm',
38
+ driverName: 'fake',
39
+ driverSpec: 'appium-fake-driver',
40
+ pluginSource: 'local',
41
+ pluginSpec: e2ehelper_1.PLUGIN_PATH,
42
+ appiumHome: APPIUM_HOME,
43
+ });
44
+ const hub_url = `http://${ip_1.default.address()}:${e2ehelper_1.HUB_APPIUM_PORT}`;
45
+ it('Should capture duration and OTel metadata for commands', () => __awaiter(void 0, void 0, void 0, function* () {
46
+ const driver = yield (0, webdriverio_1.remote)({
47
+ protocol: 'http',
48
+ hostname: ip_1.default.address(),
49
+ port: e2ehelper_1.HUB_APPIUM_PORT,
50
+ path: '/wd/hub',
51
+ capabilities: {
52
+ platformName: 'Android',
53
+ 'appium:automationName': 'fake',
54
+ 'appium:deviceName': 'FakeDevice',
55
+ 'xe:enable_dashboard': true,
56
+ },
57
+ });
58
+ try {
59
+ yield driver.getTitle();
60
+ yield driver.$('//button').click();
61
+ // Give some time for background persistence
62
+ yield new Promise((r) => setTimeout(r, 1000));
63
+ const sessionLogs = yield prisma_1.prisma.sessionLog.findMany({
64
+ where: { session_id: driver.sessionId },
65
+ orderBy: { createdAt: 'desc' },
66
+ });
67
+ console.log(`Found ${sessionLogs.length} logs for session ${driver.sessionId}`);
68
+ sessionLogs.forEach((log) => {
69
+ console.log(`Command: ${log.command_name}, Duration: ${log.duration}ms, SpanID: ${log.span_id}`);
70
+ // Assertions
71
+ (0, chai_2.expect)(log.span_id).to.not.be.null;
72
+ (0, chai_2.expect)(log.trace_id).to.not.be.null;
73
+ if (log.command_name !== 'createSession' && log.command_name !== 'deleteSession') {
74
+ (0, chai_2.expect)(log.duration).to.be.a('number').and.at.least(0);
75
+ }
76
+ });
77
+ (0, chai_2.expect)(sessionLogs.length).to.be.at.least(2);
78
+ }
79
+ finally {
80
+ yield driver.deleteSession();
81
+ }
82
+ }));
83
+ });
@@ -0,0 +1,63 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const webdriverio_1 = require("webdriverio");
13
+ /**
14
+ * Test to verify video recording functionality
15
+ * This test enables video recording and verifies that the video_recording field is populated in the database
16
+ */
17
+ const APPIUM_HOST = 'localhost';
18
+ const APPIUM_PORT = 4723;
19
+ const WDIO_PARAMS = {
20
+ connectionRetryCount: 5,
21
+ hostname: APPIUM_HOST,
22
+ port: APPIUM_PORT,
23
+ path: '/wd/hub/',
24
+ logLevel: 'info',
25
+ };
26
+ const capabilities = {
27
+ platformName: 'iOS',
28
+ 'appium:automationName': 'XCUITest',
29
+ 'appium:iPhoneOnly': true,
30
+ 'appium:app': 'https://github.com/AppiumTestDistribution/appium-demo/blob/main/vodqa.zip?raw=true',
31
+ // Note: record_video is now enabled by default, can be set to false to disable
32
+ // record_video: false, // Uncomment to disable video recording
33
+ video_resolution: '1280x720',
34
+ name: 'Video Recording Test',
35
+ build: 'video-recording-test-build',
36
+ };
37
+ describe('Video Recording Test', () => {
38
+ let driver;
39
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
40
+ driver = yield (0, webdriverio_1.remote)(Object.assign(Object.assign({}, WDIO_PARAMS), { capabilities }));
41
+ }));
42
+ it('should record video during test', () => __awaiter(void 0, void 0, void 0, function* () {
43
+ console.log('✅ Session created - video recording should be enabled');
44
+ // Perform some basic interactions
45
+ try {
46
+ yield driver.$('~login').click();
47
+ console.log('✅ Clicked login button');
48
+ }
49
+ catch (e) {
50
+ console.log('ℹ️ Could not click login button (element may not exist in test environment)');
51
+ }
52
+ // Wait a bit to ensure video is being recorded
53
+ yield driver.pause(2000);
54
+ console.log('✅ Test paused for 2 seconds - video should be recording');
55
+ }));
56
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
57
+ console.log('🟢 Deleting session - video should be stopped and saved');
58
+ yield driver.deleteSession();
59
+ console.log('✅ Session deleted');
60
+ // Give the server a moment to process the video
61
+ yield new Promise((resolve) => setTimeout(resolve, 1000));
62
+ }));
63
+ });
@@ -0,0 +1,112 @@
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.setupTestContainer = setupTestContainer;
16
+ exports.createTestAndroidManager = createTestAndroidManager;
17
+ exports.createTestIOSManager = createTestIOSManager;
18
+ exports.createTestXenonManager = createTestXenonManager;
19
+ exports.resetTestContainer = resetTestContainer;
20
+ require("reflect-metadata");
21
+ const typedi_1 = require("typedi");
22
+ const PluginContext_1 = require("../../src/PluginContext");
23
+ const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
24
+ const uuid_1 = require("uuid");
25
+ const db_1 = require("../../src/data-service/db");
26
+ const device_store_1 = require("../../src/data-service/device-store");
27
+ const AndroidDeviceManager_1 = __importDefault(require("../../src/device-managers/AndroidDeviceManager"));
28
+ const IOSDeviceManager_1 = __importDefault(require("../../src/device-managers/IOSDeviceManager"));
29
+ const device_managers_1 = require("../../src/device-managers");
30
+ const sinon_1 = __importDefault(require("sinon"));
31
+ const IOSDiscoveryService_1 = require("../../src/device-managers/ios/IOSDiscoveryService");
32
+ const sandbox = sinon_1.default.createSandbox();
33
+ /**
34
+ * Test utility to initialize the TypeDI Container with a PluginContext
35
+ * configured for testing purposes.
36
+ */
37
+ function setupTestContainer(overrides) {
38
+ // Reset container to ensure clean state between tests
39
+ typedi_1.Container.reset();
40
+ const nodeId = (0, uuid_1.v4)();
41
+ const port = 4723;
42
+ const pluginArgs = Object.assign({}, IPluginArgs_1.DefaultPluginArgs, overrides || {});
43
+ // Mock LocalStorage
44
+ typedi_1.Container.set('LocalStorage', {
45
+ getItem: (key) => null,
46
+ setItem: (key, value) => { },
47
+ removeItem: (key) => { },
48
+ });
49
+ // Initialize PluginContext
50
+ const context = typedi_1.Container.get(PluginContext_1.PluginContext);
51
+ context.setContext(pluginArgs, port, nodeId, '');
52
+ // Get managers (they'll use the PluginContext via DI)
53
+ const androidManager = typedi_1.Container.get(AndroidDeviceManager_1.default);
54
+ const iosManager = typedi_1.Container.get(IOSDeviceManager_1.default);
55
+ const xenonManager = typedi_1.Container.get(device_managers_1.XenonManager);
56
+ return {
57
+ context,
58
+ androidManager,
59
+ iosManager,
60
+ xenonManager,
61
+ nodeId,
62
+ port,
63
+ };
64
+ }
65
+ /**
66
+ * Creates a mock PluginContext and AndroidDeviceManager for unit testing.
67
+ * Use this when you need to stub/spy on the manager.
68
+ */
69
+ function createTestAndroidManager(pluginArgs) {
70
+ const { androidManager } = setupTestContainer(pluginArgs);
71
+ return androidManager;
72
+ }
73
+ /**
74
+ * Creates a mock PluginContext and IOSDeviceManager for unit testing.
75
+ */
76
+ function createTestIOSManager(pluginArgs) {
77
+ const { iosManager } = setupTestContainer(pluginArgs);
78
+ return iosManager;
79
+ }
80
+ /**
81
+ * Creates a XenonManager configured for testing.
82
+ */
83
+ function createTestXenonManager(pluginArgs) {
84
+ const { xenonManager } = setupTestContainer(pluginArgs);
85
+ xenonManager.init();
86
+ return xenonManager;
87
+ }
88
+ function resetTestContainer() {
89
+ return __awaiter(this, void 0, void 0, function* () {
90
+ sandbox.restore();
91
+ typedi_1.Container.reset();
92
+ // Stub discovery methods to prevent background pollution
93
+ sandbox.stub(AndroidDeviceManager_1.default.prototype, 'getDevices').resolves([]);
94
+ sandbox.stub(IOSDiscoveryService_1.IOSDiscoveryService.prototype, 'getDevices').resolves([]);
95
+ // @ts-ignore - Stubbing internal methods to be extra safe
96
+ sandbox.stub(AndroidDeviceManager_1.default.prototype, 'fetchAndroidDevices').resolves([]);
97
+ // @ts-ignore
98
+ sandbox.stub(IOSDiscoveryService_1.IOSDiscoveryService.prototype, 'fetchLocalIOSDevices').resolves([]);
99
+ // @ts-ignore
100
+ sandbox.stub(IOSDiscoveryService_1.IOSDiscoveryService.prototype, 'fetchLocalSimulators').resolves([]);
101
+ // Clear DeviceStoreFactory static caches to prevent cross-test pollution
102
+ // @ts-ignore - Accessing private static members for test cleanup
103
+ device_store_1.DeviceStoreFactory._deviceStore = undefined;
104
+ // @ts-ignore
105
+ device_store_1.DeviceStoreFactory._pendingSessionStore = undefined;
106
+ // @ts-ignore
107
+ device_store_1.DeviceStoreFactory._cliArgsStore = undefined;
108
+ // @ts-ignore
109
+ device_store_1.DeviceStoreFactory._healEtalonStore = undefined;
110
+ yield db_1.XenonDatabase.reset();
111
+ });
112
+ }