@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,307 @@
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 __asyncValues = (this && this.__asyncValues) || function (o) {
12
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
13
+ var m = o[Symbol.asyncIterator], i;
14
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
15
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
16
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
17
+ };
18
+ var __importDefault = (this && this.__importDefault) || function (mod) {
19
+ return (mod && mod.__esModule) ? mod : { "default": mod };
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ require("reflect-metadata");
23
+ const device_service_1 = require("../../src/data-service/device-service");
24
+ const db_1 = require("../../src/data-service/db");
25
+ const chai_1 = require("chai");
26
+ const semver_1 = __importDefault(require("semver"));
27
+ const test_container_1 = require("../helpers/test-container");
28
+ describe('Get device', () => {
29
+ before('Set devices in memory', () => __awaiter(void 0, void 0, void 0, function* () {
30
+ var _a, e_1, _b, _c;
31
+ yield (0, test_container_1.resetTestContainer)();
32
+ yield (0, test_container_1.setupTestContainer)();
33
+ const devices = [
34
+ {
35
+ sdk: '10',
36
+ realDevice: false,
37
+ name: 'sdk_gphone_x86',
38
+ busy: false,
39
+ state: 'device',
40
+ udid: 'emulator-5554',
41
+ platform: 'android',
42
+ deviceType: 'emulator',
43
+ offline: false,
44
+ userBlocked: false,
45
+ host: 'http://localhost:4723',
46
+ },
47
+ {
48
+ sdk: '11',
49
+ realDevice: false,
50
+ name: 'sdk_gphone_x86',
51
+ busy: false,
52
+ state: 'device',
53
+ udid: 'emulator-5556',
54
+ platform: 'android',
55
+ deviceType: 'emulator',
56
+ offline: false,
57
+ userBlocked: false,
58
+ host: 'http://localhost:4723',
59
+ },
60
+ {
61
+ name: 'iPhone SE (2nd generation)',
62
+ udid: 'F6A28560-7D0C-4EE9-8E1D-C1A70A350434',
63
+ state: 'Shutdown',
64
+ sdk: '13.0',
65
+ platform: 'ios',
66
+ busy: false,
67
+ offline: false,
68
+ realDevice: false,
69
+ deviceType: 'simulator',
70
+ host: 'http://localhost:4723',
71
+ userBlocked: false,
72
+ },
73
+ {
74
+ name: 'iPhone 11 Pro Max',
75
+ udid: 'F44B044A-CBC3-4F9A-96B9-448899FEDD46',
76
+ state: 'Shutdown',
77
+ sdk: '14.0',
78
+ platform: 'ios',
79
+ busy: false,
80
+ offline: false,
81
+ realDevice: false,
82
+ deviceType: 'simulator',
83
+ host: 'http://localhost:4723',
84
+ userBlocked: false,
85
+ },
86
+ {
87
+ name: 'iPhone 11 Pro',
88
+ udid: '18E788F1-92BC-4F91-B5F5-3858B2164088',
89
+ state: 'Shutdown',
90
+ sdk: '15.0',
91
+ platform: 'ios',
92
+ busy: false,
93
+ offline: false,
94
+ realDevice: false,
95
+ deviceType: 'simulator',
96
+ host: 'http://localhost:4723',
97
+ userBlocked: false,
98
+ },
99
+ {
100
+ name: 'Apple TV',
101
+ udid: '8617129A-C477-44A4-9B62-319B56987CC5',
102
+ state: 'Shutdown',
103
+ sdk: '15.0',
104
+ platform: 'tvos',
105
+ busy: false,
106
+ offline: false,
107
+ realDevice: false,
108
+ deviceType: 'simulator',
109
+ userBlocked: false,
110
+ host: 'http://localhost:4723',
111
+ },
112
+ {
113
+ deviceName: 'iPhone XS',
114
+ os_version: '15',
115
+ platform: 'ios',
116
+ host: 'http://hub-cloud.browserstack.com/wd/hub',
117
+ busy: false,
118
+ userBlocked: false,
119
+ deviceType: 'real',
120
+ capability: {
121
+ deviceName: 'iPhone XS',
122
+ os_version: '15',
123
+ platform: 'ios',
124
+ },
125
+ cloud: 'browserstack',
126
+ name: 'iPhone XS',
127
+ sdk: '15.0',
128
+ udid: 'iPhone XS',
129
+ offline: false,
130
+ },
131
+ ];
132
+ (yield db_1.XenonDatabase.DeviceModel).removeDataOnly();
133
+ try {
134
+ for (var _d = true, devices_1 = __asyncValues(devices), devices_1_1; devices_1_1 = yield devices_1.next(), _a = devices_1_1.done, !_a; _d = true) {
135
+ _c = devices_1_1.value;
136
+ _d = false;
137
+ const device = _c;
138
+ (yield db_1.XenonDatabase.DeviceModel).insert(Object.assign({}, device));
139
+ }
140
+ }
141
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
142
+ finally {
143
+ try {
144
+ if (!_d && !_a && (_b = devices_1.return)) yield _b.call(devices_1);
145
+ }
146
+ finally { if (e_1) throw e_1.error; }
147
+ }
148
+ }));
149
+ it('Get android device based on filter with minSDK', () => __awaiter(void 0, void 0, void 0, function* () {
150
+ const filterOptions = {
151
+ platform: 'android',
152
+ name: '',
153
+ busy: false,
154
+ offline: false,
155
+ minSDK: '10.0.1',
156
+ };
157
+ const device = yield (0, device_service_1.getDevice)(filterOptions);
158
+ const sdk10 = semver_1.default.coerce('10');
159
+ const sdk101 = semver_1.default.coerce('10.0.1');
160
+ const sdk11 = semver_1.default.coerce('11.0.1');
161
+ (0, chai_1.expect)(semver_1.default.gte(sdk10, sdk10)).to.be.true;
162
+ (0, chai_1.expect)(semver_1.default.gte(sdk101, sdk10)).to.be.true;
163
+ (0, chai_1.expect)(semver_1.default.gte(sdk10, sdk11)).to.be.false;
164
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.sdk).to.be.eq('11');
165
+ }));
166
+ it('Get iOS device based on filter real device', () => __awaiter(void 0, void 0, void 0, function* () {
167
+ const filterOptions = {
168
+ platform: 'ios',
169
+ name: '',
170
+ deviceType: 'real',
171
+ busy: false,
172
+ userBlocked: false,
173
+ };
174
+ const device = yield (0, device_service_1.getDevice)(filterOptions);
175
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.deviceType).to.be.eq('real');
176
+ }));
177
+ it('Get android device based on filter with minSDK and maxSDK', () => __awaiter(void 0, void 0, void 0, function* () {
178
+ const filterOptions = {
179
+ platform: 'android',
180
+ name: '',
181
+ busy: false,
182
+ offline: false,
183
+ minSDK: '10',
184
+ maxSDK: '10.0.1',
185
+ };
186
+ const device = yield (0, device_service_1.getDevice)(filterOptions);
187
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.sdk).to.be.eq('10');
188
+ }));
189
+ it('Get android device based on filter with maxSDK', () => __awaiter(void 0, void 0, void 0, function* () {
190
+ const filterOptions = {
191
+ platform: 'android',
192
+ name: '',
193
+ busy: false,
194
+ offline: false,
195
+ maxSDK: '10.0.1',
196
+ };
197
+ const device = yield (0, device_service_1.getDevice)(filterOptions);
198
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.sdk).to.be.eq('10');
199
+ }));
200
+ it('Get ios simulator based on filter with minSDK', () => __awaiter(void 0, void 0, void 0, function* () {
201
+ const filterOptions = {
202
+ platform: 'ios',
203
+ name: '',
204
+ busy: false,
205
+ offline: false,
206
+ minSDK: '14.1.0',
207
+ };
208
+ const device = yield (0, device_service_1.getDevice)(filterOptions);
209
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.sdk).to.be.eq('15.0');
210
+ }));
211
+ it('Get ios simulator based on filter with maxSDK', () => __awaiter(void 0, void 0, void 0, function* () {
212
+ const filterOptions = {
213
+ platform: 'ios',
214
+ name: '',
215
+ busy: false,
216
+ offline: false,
217
+ maxSDK: '14.1.0',
218
+ };
219
+ const device = yield (0, device_service_1.getDevice)(filterOptions);
220
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.sdk).to.be.eq('14.0');
221
+ }));
222
+ it('Get ios simulator based on filter with minSDK and maxSDK', () => __awaiter(void 0, void 0, void 0, function* () {
223
+ const filterOptions = {
224
+ platform: 'ios',
225
+ name: '',
226
+ busy: false,
227
+ offline: false,
228
+ minSDK: '14',
229
+ maxSDK: '14.1.0',
230
+ };
231
+ const device = yield (0, device_service_1.getDevice)(filterOptions);
232
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.sdk).to.be.eq('14.0');
233
+ }));
234
+ it('Get android device based on filter with platformVersion', () => __awaiter(void 0, void 0, void 0, function* () {
235
+ const filterOptions = {
236
+ platform: 'android',
237
+ name: '',
238
+ busy: false,
239
+ offline: false,
240
+ platformVersion: '10',
241
+ };
242
+ const device = yield (0, device_service_1.getDevice)(filterOptions);
243
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.sdk).to.be.eql('10');
244
+ }));
245
+ it('Get ios simulator based on filter with platform', () => __awaiter(void 0, void 0, void 0, function* () {
246
+ const filterOptions = {
247
+ platform: 'ios',
248
+ name: '',
249
+ busy: false,
250
+ offline: false,
251
+ };
252
+ const device = yield (0, device_service_1.getDevice)({
253
+ platform: 'ios',
254
+ });
255
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.sdk).to.deep.equal('15.0');
256
+ }));
257
+ it('Get ios simulator based on filter with platformVersion', () => __awaiter(void 0, void 0, void 0, function* () {
258
+ const filterOptions = {
259
+ //platform: 'ios',
260
+ name: '',
261
+ busy: false,
262
+ offline: false,
263
+ platformVersion: '14.0',
264
+ };
265
+ const device = yield (0, device_service_1.getDevice)(filterOptions);
266
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.sdk).to.be.eql('14.0');
267
+ }));
268
+ it('Get android device returns undefined based on filter with platformVersion', () => __awaiter(void 0, void 0, void 0, function* () {
269
+ const filterOptions = {
270
+ platform: 'android',
271
+ name: '',
272
+ busy: false,
273
+ offline: false,
274
+ platformVersion: '9',
275
+ };
276
+ const device = yield (0, device_service_1.getDevice)(filterOptions);
277
+ (0, chai_1.expect)(device).to.be.undefined;
278
+ }));
279
+ it('Get ios simulator returns undefined based on filter with platformVersion', () => __awaiter(void 0, void 0, void 0, function* () {
280
+ const filterOptions = {
281
+ platform: 'ios',
282
+ name: '',
283
+ busy: false,
284
+ offline: false,
285
+ platformVersion: '16.0',
286
+ };
287
+ const device = yield (0, device_service_1.getDevice)(filterOptions);
288
+ (0, chai_1.expect)(device).to.be.undefined;
289
+ }));
290
+ it('Get apple tv simulator based on filter with platformName', () => __awaiter(void 0, void 0, void 0, function* () {
291
+ const filterOptions = {
292
+ platform: 'tvos',
293
+ name: '',
294
+ busy: false,
295
+ offline: false,
296
+ };
297
+ const device = yield (0, device_service_1.getDevice)(filterOptions);
298
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.platform).to.be.eql('tvos');
299
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.sdk).to.be.eql('15.0');
300
+ if (device === null || device === void 0 ? void 0 : device.sdk) {
301
+ (0, chai_1.expect)(parseFloat(device === null || device === void 0 ? void 0 : device.sdk)).to.be.gte(14.1);
302
+ }
303
+ else {
304
+ (0, chai_1.expect)(device === null || device === void 0 ? void 0 : device.sdk).to.be.not.undefined;
305
+ }
306
+ }));
307
+ });
@@ -0,0 +1,313 @@
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 __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ require("reflect-metadata");
49
+ const sinon_1 = __importDefault(require("sinon"));
50
+ const DeviceUtils = __importStar(require("../../src/device-utils"));
51
+ const DeviceService = __importStar(require("../../src/data-service/device-service"));
52
+ const chai_1 = __importDefault(require("chai"));
53
+ const sinon_chai_1 = __importDefault(require("sinon-chai"));
54
+ const db_1 = require("../../src/data-service/db");
55
+ const ip_1 = __importDefault(require("ip"));
56
+ const device_service_1 = require("../../src/data-service/device-service");
57
+ const device_utils_1 = require("../../src/device-utils");
58
+ const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
59
+ const test_container_1 = require("../helpers/test-container");
60
+ chai_1.default.should();
61
+ chai_1.default.use(sinon_chai_1.default);
62
+ const expect = chai_1.default.expect;
63
+ const sandbox = sinon_1.default.createSandbox();
64
+ describe('Device Utils', () => {
65
+ const hub1Device = {
66
+ systemPort: 56205,
67
+ sdk: '10',
68
+ realDevice: true,
69
+ name: 'emulator-5555',
70
+ busy: false,
71
+ state: 'device',
72
+ udid: 'emulator-5555',
73
+ platform: 'android',
74
+ deviceType: 'real',
75
+ host: 'http://192.168.0.225:4723',
76
+ totalUtilizationTimeMilliSec: 40778,
77
+ sessionStartTime: 1667113345897,
78
+ offline: false,
79
+ lastCmdExecutedAt: 1667113356356,
80
+ };
81
+ const hub2Device = {
82
+ systemPort: 56205,
83
+ sdk: '10',
84
+ realDevice: true,
85
+ name: 'emulator-5555',
86
+ busy: false,
87
+ state: 'device',
88
+ udid: 'emulator-5555',
89
+ platform: 'android',
90
+ deviceType: 'real',
91
+ host: 'http://192.168.0.226:4723',
92
+ totalUtilizationTimeMilliSec: 40778,
93
+ sessionStartTime: 1667113345897,
94
+ offline: false,
95
+ lastCmdExecutedAt: 1667113356356,
96
+ };
97
+ const localDeviceiOS = {
98
+ name: 'iPhone SE (3rd generation)',
99
+ udid: '14C1078F-74C1-4672-BDB7-B65FC85FBFB4',
100
+ state: 'Shutdown',
101
+ sdk: '16.0',
102
+ platform: 'ios',
103
+ wdaLocalPort: 53712,
104
+ busy: false,
105
+ realDevice: false,
106
+ deviceType: 'simulator',
107
+ host: `http://${ip_1.default.address()}:4723`,
108
+ totalUtilizationTimeMilliSec: 0,
109
+ sessionStartTime: 0,
110
+ offline: false,
111
+ };
112
+ // device with host
113
+ const noHostDevice = {
114
+ systemPort: 56205,
115
+ sdk: '10',
116
+ realDevice: true,
117
+ name: 'emulator-9999',
118
+ busy: false,
119
+ state: 'device',
120
+ udid: 'emulator-9999',
121
+ platform: 'android',
122
+ deviceType: 'real',
123
+ totalUtilizationTimeMilliSec: 40778,
124
+ sessionStartTime: 1667113345897,
125
+ offline: false,
126
+ lastCmdExecutedAt: 1667113356356,
127
+ userBlocked: false,
128
+ host: '127.0.0.1',
129
+ };
130
+ const emulator5555 = {
131
+ systemPort: 56206,
132
+ sdk: '11',
133
+ realDevice: false,
134
+ name: 'emulator-5555',
135
+ busy: false,
136
+ state: 'device',
137
+ udid: 'emulator-5555',
138
+ platform: 'android',
139
+ deviceType: 'simulator',
140
+ host: '192.168.0.226',
141
+ totalUtilizationTimeMilliSec: 0,
142
+ sessionStartTime: 0,
143
+ offline: false,
144
+ userBlocked: false,
145
+ };
146
+ const devices = [
147
+ hub1Device,
148
+ hub2Device,
149
+ localDeviceiOS,
150
+ noHostDevice,
151
+ emulator5555,
152
+ ];
153
+ const pluginArgs = Object.assign({}, IPluginArgs_1.DefaultPluginArgs, {
154
+ remote: [`http://${ip_1.default.address()}:4723`],
155
+ iosDeviceType: 'both',
156
+ androidDeviceType: 'both',
157
+ });
158
+ afterEach(function () {
159
+ sandbox.restore();
160
+ });
161
+ before(() => __awaiter(void 0, void 0, void 0, function* () {
162
+ yield (0, test_container_1.resetTestContainer)();
163
+ yield (0, test_container_1.setupTestContainer)();
164
+ }));
165
+ it('Allocate devices for session with host filter', () => __awaiter(void 0, void 0, void 0, function* () {
166
+ (yield db_1.XenonDatabase.DeviceModel).removeDataOnly();
167
+ const deviceManager = (0, test_container_1.createTestXenonManager)(Object.assign({}, pluginArgs, { maxSessions: 3, platform: 'android' }));
168
+ yield (0, device_service_1.addNewDevice)(devices);
169
+ const capabilities = {
170
+ alwaysMatch: {
171
+ platformName: 'android',
172
+ 'appium:app': '/Downloads/VodQA.apk',
173
+ 'appium:deviceAvailabilityTimeout': 1800,
174
+ 'appium:deviceRetryInterval': 100,
175
+ 'appium:filterByHost': '192.168.0.226',
176
+ },
177
+ firstMatch: [{}],
178
+ };
179
+ const allocatedDeviceForFirstSession = yield DeviceUtils.allocateDeviceForSession(capabilities, 1000, 1000, pluginArgs);
180
+ function getFilteredDevice(udid, host) {
181
+ return __awaiter(this, void 0, void 0, function* () {
182
+ return (yield db_1.XenonDatabase.DeviceModel).chain().find({ udid, host }).data();
183
+ });
184
+ }
185
+ const foundDevice = (yield getFilteredDevice(allocatedDeviceForFirstSession.udid, allocatedDeviceForFirstSession.host))[0];
186
+ expect(foundDevice.busy).to.be.true;
187
+ yield (0, device_utils_1.allocateDeviceForSession)(capabilities, 1000, 1000, pluginArgs).catch((error) => expect(error)
188
+ .to.be.an('error')
189
+ .with.property('message', 'Device is busy or blocked.. Device request: {"platform":"android","udid":"emulator-5555","filterByHost":"192.168.0.226"}'));
190
+ }));
191
+ it('Allocating device should set device to be busy', function () {
192
+ return __awaiter(this, void 0, void 0, function* () {
193
+ (yield db_1.XenonDatabase.DeviceModel).removeDataOnly();
194
+ const deviceManager = (0, test_container_1.createTestXenonManager)(Object.assign({}, pluginArgs, { maxSessions: 3, platform: 'android' }));
195
+ yield (0, device_service_1.addNewDevice)(devices);
196
+ const capabilities = {
197
+ alwaysMatch: {
198
+ platformName: 'android',
199
+ 'appium:app': '/Downloads/VodQA.apk',
200
+ 'appium:deviceAvailabilityTimeout': 1800,
201
+ 'appium:deviceRetryInterval': 100,
202
+ },
203
+ firstMatch: [{}],
204
+ };
205
+ const allocatedDeviceForFirstSession = yield DeviceUtils.allocateDeviceForSession(capabilities, 1000, 1000, pluginArgs);
206
+ function getFilteredDevice(udid, host) {
207
+ return __awaiter(this, void 0, void 0, function* () {
208
+ return (yield db_1.XenonDatabase.DeviceModel).chain().find({ udid, host }).data();
209
+ });
210
+ }
211
+ const foundDevice = (yield getFilteredDevice(allocatedDeviceForFirstSession.udid, allocatedDeviceForFirstSession.host))[0];
212
+ expect(foundDevice.busy).to.be.true;
213
+ let filterDeviceWithSameUDID = (yield db_1.XenonDatabase.DeviceModel)
214
+ .chain()
215
+ .find({ udid: allocatedDeviceForFirstSession.udid })
216
+ .data();
217
+ expect(filterDeviceWithSameUDID.length).to.be.greaterThanOrEqual(1);
218
+ // one device should be busy and the other is not
219
+ filterDeviceWithSameUDID.filter((device) => device.busy).length.should.be.equal(1);
220
+ const allocatedDeviceForSecondSession = yield DeviceUtils.allocateDeviceForSession(capabilities, 1000, 1000, pluginArgs);
221
+ // allocatedDeviceForSecondSession should not be the same as allocatedDeviceForFirstSession
222
+ expect(allocatedDeviceForFirstSession).to.not.be.equal(allocatedDeviceForSecondSession);
223
+ const foundSecondDevice = (yield db_1.XenonDatabase.DeviceModel)
224
+ .chain()
225
+ .find({
226
+ udid: allocatedDeviceForSecondSession.udid,
227
+ host: allocatedDeviceForSecondSession.host,
228
+ })
229
+ .data()[0];
230
+ expect(foundSecondDevice.busy).to.be.true;
231
+ yield (0, device_utils_1.allocateDeviceForSession)(capabilities, 1000, 1000, pluginArgs).catch((error) => expect(error)
232
+ .to.be.an('error')
233
+ .with.property('message', `Device is busy or blocked.. Device request: {"platform":"android","udid":"${allocatedDeviceForFirstSession.udid}","filterByHost":"192.168.0.226"}`));
234
+ });
235
+ });
236
+ it('should release blocked devices that have no activity for more than the timeout', () => __awaiter(void 0, void 0, void 0, function* () {
237
+ (yield db_1.XenonDatabase.DeviceModel).removeDataOnly();
238
+ // mock setUtilizationTime
239
+ sandbox.stub(DeviceUtils, 'setUtilizationTime').callsFake(sinon_1.default.fake());
240
+ const unbusyDevices = devices.map((device) => (Object.assign(Object.assign({}, device), { busy: false })));
241
+ yield (0, device_service_1.addNewDevice)(unbusyDevices);
242
+ const targetDevice = unbusyDevices[0];
243
+ yield (yield db_1.XenonDatabase.DeviceModel)
244
+ .chain()
245
+ .find({ udid: targetDevice.udid, host: targetDevice.host })
246
+ .update(function (device) {
247
+ device.busy = true;
248
+ device.lastCmdExecutedAt = new Date().getTime() - 100000;
249
+ });
250
+ const releaseBlockedDevicesMock = sandbox.spy(DeviceService, 'unblockDevice');
251
+ yield DeviceUtils.releaseBlockedDevices(20);
252
+ releaseBlockedDevicesMock.should.have.been.calledWith(targetDevice.udid, targetDevice.host);
253
+ }));
254
+ it('should release device on node that is not used for more than the timeout', () => __awaiter(void 0, void 0, void 0, function* () {
255
+ (yield db_1.XenonDatabase.DeviceModel).removeDataOnly();
256
+ // mock setUtilizationTime
257
+ sandbox.stub(DeviceUtils, 'setUtilizationTime').callsFake(sinon_1.default.fake());
258
+ const unbusyDevices = devices.map((device) => (Object.assign(Object.assign({}, device), { busy: false })));
259
+ const deviceOnAnotherNode = Object.assign(Object.assign({}, unbusyDevices[0]), { host: 'http://anotherhost:4723' });
260
+ unbusyDevices.push(deviceOnAnotherNode);
261
+ yield (0, device_service_1.addNewDevice)(unbusyDevices);
262
+ yield (yield db_1.XenonDatabase.DeviceModel)
263
+ .chain()
264
+ .find({ udid: deviceOnAnotherNode.udid, host: deviceOnAnotherNode.host })
265
+ .update(function (device) {
266
+ device.busy = true;
267
+ device.lastCmdExecutedAt = new Date().getTime() - 100000;
268
+ });
269
+ const unblockDeviceMock = sandbox.spy(DeviceService, 'unblockDevice');
270
+ yield DeviceUtils.releaseBlockedDevices(20);
271
+ unblockDeviceMock.should.have.been.calledWith(deviceOnAnotherNode.udid, deviceOnAnotherNode.host);
272
+ }));
273
+ it('Block and unblock device', () => __awaiter(void 0, void 0, void 0, function* () {
274
+ (yield db_1.XenonDatabase.DeviceModel).removeDataOnly();
275
+ // mock setUtilizationTime
276
+ sandbox.stub(DeviceUtils, 'setUtilizationTime').callsFake(sinon_1.default.fake());
277
+ const unbusyDevices = devices.map((device) => (Object.assign(Object.assign({}, device), { busy: false })));
278
+ yield (0, device_service_1.addNewDevice)(unbusyDevices);
279
+ const targetDevice = unbusyDevices[0];
280
+ // action: block device
281
+ yield DeviceService.blockDevice(targetDevice.udid, targetDevice.host);
282
+ // assert device is busy
283
+ expect((yield db_1.XenonDatabase.DeviceModel)
284
+ .chain()
285
+ .find({ udid: targetDevice.udid, host: targetDevice.host })
286
+ .data()[0]).to.have.property('busy', true);
287
+ // set lastCommandTimestamp, otherwise it won't be picked up as device to unblock
288
+ (yield db_1.XenonDatabase.DeviceModel)
289
+ .chain()
290
+ .find({ udid: targetDevice.udid, host: targetDevice.host })
291
+ .update(function (device) {
292
+ device.lastCmdExecutedAt = new Date().getTime();
293
+ });
294
+ let unblockCandidates = yield DeviceUtils.unblockCandidateDevices();
295
+ // assert: device should be part of candidate list to unblock
296
+ expect(unblockCandidates.map((item) => item.udid)).to.include(targetDevice.udid);
297
+ // action: release blocked devices
298
+ yield DeviceService.unblockDevice(targetDevice.udid, targetDevice.host);
299
+ // assert: device should not be part of candidate list to unblock
300
+ unblockCandidates = yield DeviceUtils.unblockCandidateDevices();
301
+ expect(unblockCandidates.map((item) => item.udid)).to.not.include(targetDevice.udid);
302
+ const device = (yield db_1.XenonDatabase.DeviceModel)
303
+ .chain()
304
+ .find({ udid: targetDevice.udid, host: targetDevice.host })
305
+ .data()[0];
306
+ expect(device).to.be.not.undefined;
307
+ expect(device === null || device === void 0 ? void 0 : device.busy).to.be.false;
308
+ }));
309
+ it('should remove stale devices', () => __awaiter(void 0, void 0, void 0, function* () {
310
+ (yield db_1.XenonDatabase.DeviceModel).removeDataOnly();
311
+ expect((yield db_1.XenonDatabase.DeviceModel).chain().find({ udid: 'emulator-9999' }).data().length).to.be.equal(0);
312
+ }));
313
+ });
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ module.exports = {
3
+ platform: 'android',
4
+ };