@xenon-device-management/xenon 2.12.0 → 2.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +223 -488
- package/lib/package.json +2 -2
- package/lib/public/assets/{AnnotationOverlay-Crkn72_3.js → AnnotationOverlay-Cvpkk4r9.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-BThVlj_j.js → ApiKeyGate-Bhn5ZtVM.js} +1 -1
- package/lib/public/assets/{BugReportButton-Dwa0GnBh.js → BugReportButton-BA6aEjTC.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-Ccc-pOls.js → DeviceMosaicView-ChhHnpFQ.js} +1 -1
- package/lib/public/assets/{EmptyState-BeuTrlN_.js → EmptyState-CgzbuUvO.js} +1 -1
- package/lib/public/assets/{FieldGroup-DBIaNxl1.js → FieldGroup-BHc_5qNc.js} +1 -1
- package/lib/public/assets/{FilterMenu-BgSAS6iT.js → FilterMenu-D7OMGuRc.js} +1 -1
- package/lib/public/assets/{Menu-GCbP6gYN.js → Menu-C173JJ0l.js} +1 -1
- package/lib/public/assets/{Modal-ByESeBRC.js → Modal-ChvVUI3B.js} +1 -1
- package/lib/public/assets/{RecordingPage-Bwyf3WMl.js → RecordingPage-Dhw4d_O6.js} +1 -1
- package/lib/public/assets/{RecordingsPage-7kM1rB3i.js → RecordingsPage-z97ftGK-.js} +2 -2
- package/lib/public/assets/{SegmentedControl-B8tTU8kD.js → SegmentedControl-DbXhBGZC.js} +1 -1
- package/lib/public/assets/{SettingCard-Clg_Ijbc.js → SettingCard-C908GgiI.js} +1 -1
- package/lib/public/assets/{Table-CIaR0QWV.js → Table-B1DB_ZN-.js} +1 -1
- package/lib/public/assets/{activity-Bengh9SD.js → activity-Css0xNvp.js} +1 -1
- package/lib/public/assets/ai-settings-BMQK3-yN.js +21 -0
- package/lib/public/assets/{api-keys-CrGlbfn-.js → api-keys-CbJ42eDQ.js} +1 -1
- package/lib/public/assets/{apps-DIbcGVlU.js → apps-DW1pw2re.js} +2 -2
- package/lib/public/assets/{arrow-left-BtJIXXMY.js → arrow-left-Dpog9L0F.js} +1 -1
- package/lib/public/assets/{arrow-right-zm-NagQh.js → arrow-right-CsBIlSIw.js} +1 -1
- package/lib/public/assets/{arrow-up-right-DdU8YEYp.js → arrow-up-right-z1tRQE4R.js} +1 -1
- package/lib/public/assets/{auth-shell-CHmBFmOz.js → auth-shell-mXiX5vNk.js} +2 -2
- package/lib/public/assets/{builds-page-JkzWKRpU.js → builds-page-BcFn23bE.js} +1 -1
- package/lib/public/assets/{button-CqhPBGRj.js → button-Cq_zHETv.js} +1 -1
- package/lib/public/assets/{calendar-C7eYYJTo.js → calendar-CKxABX9q.js} +1 -1
- package/lib/public/assets/{check-DpbIM4E0.js → check-C7xu-hGU.js} +1 -1
- package/lib/public/assets/{chevron-right-Dl4X1PRz.js → chevron-right-BiqfdCGA.js} +1 -1
- package/lib/public/assets/{circle-check-Du3sjbfV.js → circle-check-CyBjjPxR.js} +1 -1
- package/lib/public/assets/{circle-x-BSSqhpXi.js → circle-x-6gMRcx6z.js} +1 -1
- package/lib/public/assets/{clock-DMT60v1C.js → clock-U1rDTT1h.js} +1 -1
- package/lib/public/assets/{copy-BKNyOehd.js → copy-BIM0ZX0L.js} +1 -1
- package/lib/public/assets/device-explorer-DSpAA_t9.js +279 -0
- package/lib/public/assets/{download-BM6Xn22t.js → download-DDBRXnAZ.js} +1 -1
- package/lib/public/assets/{forgot-password-B8WqMqBT.js → forgot-password-Dpg7yxM8.js} +1 -1
- package/lib/public/assets/{index-ClrpAMAT.js → index-BGBqohfQ.js} +43 -43
- package/lib/public/assets/{index-DauQh6ie.js → index-BQ2luT7L.js} +1 -1
- package/lib/public/assets/{input-CyKdLnEx.js → input-BvB1cjHE.js} +1 -1
- package/lib/public/assets/{line-chart-EIBXwYGo.js → line-chart-m78VggnW.js} +1 -1
- package/lib/public/assets/{list-checks-fWsgD9bI.js → list-checks-cY7o1QTL.js} +1 -1
- package/lib/public/assets/{lock-CVCe56TH.js → lock-loz8txQm.js} +1 -1
- package/lib/public/assets/{login-BJ8a7yVD.js → login-BYOvz4lx.js} +1 -1
- package/lib/public/assets/maintenance-settings-BxbA7SkB.js +6 -0
- package/lib/public/assets/{monitor-Bw1YQZnL.js → monitor-C4HPHb6s.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-C01jbqkO.js → mouse-pointer-2-BkdKtCzi.js} +1 -1
- package/lib/public/assets/{network-CBGUjJDJ.js → network-C5e0MIWo.js} +1 -1
- package/lib/public/assets/{overview-Cxe8aQ7C.js → overview-tlu8ijGd.js} +1 -1
- package/lib/public/assets/{page-header-B92DKLiq.js → page-header-FXPlo6az.js} +1 -1
- package/lib/public/assets/{play-Ck0L-0_m.js → play-CWdk0_Sy.js} +1 -1
- package/lib/public/assets/{plus-Dq3tCy2N.js → plus-B2xCnntV.js} +1 -1
- package/lib/public/assets/{profile-page--dkDiKbr.js → profile-page-D080qzr4.js} +1 -1
- package/lib/public/assets/{recording-group-store-5BYIFFN9.js → recording-group-store-B75435xR.js} +1 -1
- package/lib/public/assets/{reset-password-KYWlfjia.js → reset-password-D3u4Mbvn.js} +1 -1
- package/lib/public/assets/{runbook-page-mnsxgS1d.js → runbook-page-hPRZMy7s.js} +1 -1
- package/lib/public/assets/{select-BBOIZTYm.js → select-12RThAXc.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-CcI2j_Su.js → selector-detail-redirect-uQwvV7EQ.js} +1 -1
- package/lib/public/assets/{selector-health-page-BdJb_x5L.js → selector-health-page-CThoxDFO.js} +2 -2
- package/lib/public/assets/{session-detail-page-BjLaJy8z.js → session-detail-page-Bc02YjMm.js} +1 -1
- package/lib/public/assets/settings-C1VIluuT.js +6 -0
- package/lib/public/assets/{stat-tile-BU9e4s36.js → stat-tile-B02WEte8.js} +1 -1
- package/lib/public/assets/{tablet-hgbEwrWq.js → tablet-Dj30VGZl.js} +1 -1
- package/lib/public/assets/{teams-uiiG4hZM.js → teams-uL4fjTDR.js} +1 -1
- package/lib/public/assets/{trash-2-NK_Iazmg.js → trash-2-B54e-TfY.js} +1 -1
- package/lib/public/assets/{upload-BUX8TNFi.js → upload-B3k8WCSP.js} +1 -1
- package/lib/public/assets/{use-builds-data-DZszmFyl.js → use-builds-data-BFn90NH3.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-BK4B9kRC.js → use-password-reset-mode-DL2TmWZ0.js} +1 -1
- package/lib/public/assets/{users-BU4XBbMT.js → users-CqBuxMlX.js} +1 -1
- package/lib/public/assets/{users-BDh1xjad.js → users-DjQSucKb.js} +1 -1
- package/lib/public/assets/{video-off-BOjNwT4R.js → video-off-T0rlt0GA.js} +1 -1
- package/lib/public/assets/webhook-settings-DAHgnJyZ.js +1 -0
- package/lib/public/assets/{zap-DTWwVMUg.js → zap-gBHqutZV.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/src/app/apiErrors.js +118 -0
- package/lib/src/app/index.js +6 -1
- package/lib/src/app/openapi/control.yaml +3129 -0
- package/lib/src/app/openapi/grid.yaml +2295 -0
- package/lib/src/app/openapi/identity.yaml +2168 -0
- package/lib/src/app/openapi/platform.yaml +2885 -0
- package/lib/src/app/openapi/sessions.yaml +3784 -0
- package/lib/src/app/routers/bug-report.js +4 -1
- package/lib/src/app/routers/config.js +6 -107
- package/lib/src/app/routers/control.js +117 -59
- package/lib/src/app/routers/dashboard.js +13 -7
- package/lib/src/app/routers/grid.js +54 -14
- package/lib/src/app/routers/profile.js +27 -13
- package/lib/src/app/routers/recordings.js +11 -5
- package/lib/src/app/routers/reservation.js +63 -15
- package/lib/src/app/routers/users.js +4 -0
- package/lib/src/app/routers/webhook.js +17 -8
- package/lib/src/app/swagger.js +259 -177
- package/lib/src/data-service/device-service.js +4 -1
- package/lib/src/data-service/deviceFieldOwners.js +1 -0
- package/lib/src/device-managers/AndroidDeviceManager.js +5 -2
- package/lib/src/device-managers/ios/WDAClient.js +153 -41
- package/lib/src/generated/client/edge.js +4 -3
- package/lib/src/generated/client/index-browser.js +1 -0
- package/lib/src/generated/client/index.d.ts +38 -0
- package/lib/src/generated/client/index.js +4 -3
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +2 -0
- package/lib/src/generated/client/wasm.js +1 -0
- package/lib/src/middleware/csrfMiddleware.js +13 -5
- package/lib/src/middleware/rateLimitMiddleware.js +19 -5
- package/lib/src/middleware/roleGuard.js +20 -0
- package/lib/src/services/AIService.js +13 -3
- package/lib/src/services/NotificationService.js +28 -28
- package/lib/src/services/bug-report/BugReportService.js +8 -2
- package/lib/src/services/lease/LeaseService.js +71 -20
- package/lib/src/services/omni-vision/OmniVisionService.js +14 -5
- package/lib/src/services/recording/RecordingOrchestrator.js +16 -2
- package/lib/test/helpers/expressRoutes.js +41 -0
- package/lib/test/integration/team-visibility-control.spec.js +2 -2
- package/lib/test/unit/access-scopes.spec.js +227 -0
- package/lib/test/unit/api-error-handling.spec.js +179 -0
- package/lib/test/unit/bug-report/route.spec.js +29 -0
- package/lib/test/unit/bug-report/service.spec.js +30 -0
- package/lib/test/unit/control-honest-answers.spec.js +134 -0
- package/lib/test/unit/device-allocation-routes.spec.js +232 -0
- package/lib/test/unit/healing-state-endpoints.spec.js +8 -5
- package/lib/test/unit/install-repository-app-team.spec.js +4 -1
- package/lib/test/unit/lease/LeaseService.spec.js +5 -4
- package/lib/test/unit/lease/lease-device-match.spec.js +161 -0
- package/lib/test/unit/lease/lease-session-ownership.spec.js +10 -9
- package/lib/test/unit/omni-vision-failures.spec.js +81 -0
- package/lib/test/unit/openapi-coverage.spec.js +114 -0
- package/lib/test/unit/profile-router.test.js +42 -0
- package/lib/test/unit/rateLimitMiddleware.test.js +49 -0
- package/lib/test/unit/recording-orchestrator.spec.js +80 -0
- package/lib/test/unit/recordings-library-routes.spec.js +37 -0
- package/lib/test/unit/reservation-team-visibility.spec.js +4 -3
- package/lib/test/unit/reset-link.test.js +2 -1
- package/lib/test/unit/stream-ticket-identity.spec.js +1 -1
- package/lib/test/unit/users-router.test.js +14 -1
- package/lib/test/unit/wda-client-failures.spec.js +90 -0
- package/lib/test/unit/wda-client-session.spec.js +128 -0
- package/lib/test/unit/wda-clipboard-write.spec.js +135 -0
- package/lib/test/unit/webhook-delivery.spec.js +142 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/prisma/migrations/20261004120000_reservation_holder/migration.sql +2 -0
- package/prisma/schema.prisma +2 -0
- package/scripts/dev/readme-screenshots.js +231 -0
- package/lib/public/assets/ai-settings-G9SymiPH.js +0 -21
- package/lib/public/assets/device-explorer-CoPR8DW3.js +0 -279
- package/lib/public/assets/maintenance-settings-DcRmTLS6.js +0 -6
- package/lib/public/assets/settings-B6cpMhsx.js +0 -6
- package/lib/public/assets/webhook-settings-BMzyUcx5.js +0 -1
- package/lib/src/app/swagger-docs.js +0 -1701
package/README.md
CHANGED
|
@@ -1,580 +1,315 @@
|
|
|
1
|
-
# Xenon
|
|
2
|
-
|
|
3
1
|
<h1 align="center">
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Intelligent Mobile Infrastructure
|
|
9
|
-
<br>
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.svg">
|
|
4
|
+
<img src="assets/logo-light.svg" alt="Xenon" width="280">
|
|
5
|
+
</picture>
|
|
10
6
|
</h1>
|
|
11
7
|
|
|
12
8
|
<p align="center">
|
|
13
|
-
<strong>
|
|
9
|
+
<strong>Run your mobile device lab from one place: allocation, live control, recording and self-healing tests, as an Appium 3 plugin.</strong>
|
|
14
10
|
</p>
|
|
15
11
|
|
|
16
12
|
<p align="center">
|
|
17
|
-
<a href="
|
|
18
|
-
<a href="
|
|
19
|
-
<a href="
|
|
20
|
-
<a href="
|
|
21
|
-
<a href="#documentation">Documentation</a> •
|
|
22
|
-
<a href="#contributing">Contributing</a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/@xenon-device-management/xenon"><img alt="npm" src="https://img.shields.io/npm/v/@xenon-device-management/xenon?label=npm"></a>
|
|
14
|
+
<a href="https://github.com/Rabindra184/xenon/actions/workflows/npm-publish.yml"><img alt="Publish" src="https://img.shields.io/github/actions/workflow/status/Rabindra184/xenon/npm-publish.yml?branch=main&label=publish"></a>
|
|
15
|
+
<a href="https://appium.io"><img alt="Appium 3" src="https://img.shields.io/badge/appium-3.x-662d91"></a>
|
|
16
|
+
<a href="LICENSE"><img alt="License: ISC" src="https://img.shields.io/badge/license-ISC-blue"></a>
|
|
23
17
|
</p>
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
### Why Xenon?
|
|
32
|
-
|
|
33
|
-
| Problem | Xenon Solution |
|
|
34
|
-
|---------|----------------|
|
|
35
|
-
| Tests fail due to device state | **Auto-recovery** - Devices heal themselves |
|
|
36
|
-
| Manual device management | **Smart allocation** - Queue, reserve, prioritize |
|
|
37
|
-
| Debugging is painful | **Interactive control** - Live stream, touch, shell |
|
|
38
|
-
| No visibility into failures | **Rich artifacts** - Video, screenshots, profiling |
|
|
39
|
-
| Infrastructure silos | **Unified dashboard** - One view for all devices |
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
## 🚀 Features
|
|
44
|
-
|
|
45
|
-
### Device Orchestration
|
|
46
|
-
- ✅ **Automatic device discovery** - Android (USB + emulators), iOS (devices + simulators)
|
|
47
|
-
- ✅ **Smart session allocation** - Queue management with ETA
|
|
48
|
-
- ✅ **WebSocket-First Sync** - Real-time Hub-Node-Dashboard bidirectional sync
|
|
49
|
-
- ✅ **Device reservation** - Manual mode for debugging
|
|
50
|
-
- ✅ **Team-based quotas** - Fair resource sharing
|
|
51
|
-
|
|
52
|
-
### Interactive Control
|
|
53
|
-
- ✅ **Live streaming** - Real-time device screen in browser
|
|
54
|
-
- ✅ **Touch interaction** - Tap, swipe, scroll remotely
|
|
55
|
-
- ✅ **App management** - Install, uninstall, clear data
|
|
56
|
-
- ✅ **Interactive Shell** - Execute ADB/iOS commands directly
|
|
57
|
-
- ✅ **Device information** - Battery, storage, network status
|
|
58
|
-
|
|
59
|
-
### 🧠 AI Self-Healing (Flagship)
|
|
60
|
-
- ✅ **5-Tier Healing Orchestration** - From DOM to LLM recovery
|
|
61
|
-
- ✅ **Signature-Based Learning** - Persistent "Etalon" signatures for high-confidence recovery
|
|
62
|
-
- ✅ **Multi-Modal Fallback** - Syntactic -> OCR -> Visual AI -> LLM reasoning
|
|
63
|
-
- ✅ **Infrastructure-Free** - Works with existing LokiJS (local) or PostgreSQL (remote)
|
|
64
|
-
|
|
65
|
-
#### Selector lifecycle (Trust & Truth layer)
|
|
66
|
-
|
|
67
|
-
Healed selectors flow through a state machine: **Active → Pending → Resolved**, with an optional **Muted** branch.
|
|
68
|
-
|
|
69
|
-
- **Active** — a hot selector that's healing in flight; surfaces on the Selector Health dashboard, the CI gate, and the webhook digest.
|
|
70
|
-
- **Mark as Fixed** — after rewriting the selector in your test source, click "Mark as Fixed" in the dashboard. The row moves to **Pending**.
|
|
71
|
-
- **Pending** — Xenon watches subsequent CI builds. When 3 distinct `build_id`s have run the selector with no heals, it auto-promotes to **Resolved** (`SelectorVerificationJob` runs every 15 minutes).
|
|
72
|
-
- **Resolved** — the rewrite stuck. Excluded from the CI gate and digest. A future heal flips the row back to Active with a regression badge (institutional memory).
|
|
73
|
-
- **Muted** — a selector you've intentionally chosen to ignore (legacy flow, etc.). Hidden from dashboard, CI gate, and digest until unmuted.
|
|
74
|
-
|
|
75
|
-
The dashboard shows strategy + value (`Accessibility ID: login-btn`) so suggested rewrites are copy-ready in JS / Java / Python / C# / Ruby — your client language is remembered in `localStorage.xenon.copyLang`.
|
|
76
|
-
|
|
77
|
-
> **CI gate behavior change:** muted/pending/resolved selectors no longer count toward `/healing/hotspots/violations`. If your CI was passing/failing based on that endpoint, the signal will get quieter after this release — selectors actively being managed are no longer flagged. Pass `?status=all` to opt back into the old behavior.
|
|
78
|
-
|
|
79
|
-
### Recording & Artifacts
|
|
80
|
-
- ✅ **Video recording** - Full session capture
|
|
81
|
-
- ✅ **Screenshot capture** - On-demand and per-command
|
|
82
|
-
- ✅ **Network interceptor** - Live HTTP/HTTPS capture, mocking, and HAR export ([docs](https://xenon-docs.vercel.app/docs/network-interceptor))
|
|
83
|
-
- ✅ **Distributed Tracing** - OpenTelemetry spans for exact command latency
|
|
84
|
-
- ✅ **Performance profiling** - CPU, memory, FPS metrics
|
|
85
|
-
- ✅ **Log aggregation** - Appium, device, app logs
|
|
86
|
-
- ✅ **OpenTelemetry Integration** - Standardized distributed tracing for all sessions
|
|
87
|
-
|
|
88
|
-
### Intelligence (Roadmap)
|
|
89
|
-
- 🔲 **Flaky test detection** - Auto-identify unstable tests
|
|
90
|
-
- 🔲 **Error categorization** - Crash vs timeout vs element not found
|
|
91
|
-
- 🔲 **Predictive health** - USB/battery failure prediction
|
|
92
|
-
|
|
93
|
-
---
|
|
19
|
+
<p align="center">
|
|
20
|
+
<a href="#quick-start">Quick start</a> ·
|
|
21
|
+
<a href="https://xenon-6e6.pages.dev">Documentation</a> ·
|
|
22
|
+
<a href="#api">API reference</a> ·
|
|
23
|
+
<a href="CHANGELOG.md">Changelog</a>
|
|
24
|
+
</p>
|
|
94
25
|
|
|
95
|
-
|
|
26
|
+
<p align="center">
|
|
27
|
+
<picture>
|
|
28
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/dashboard-dark.png">
|
|
29
|
+
<img src="assets/dashboard-light.png" alt="The Devices page of the Xenon dashboard: six phones, tablets and emulators, ready, busy with a test or reserved" width="100%">
|
|
30
|
+
</picture>
|
|
31
|
+
</p>
|
|
96
32
|
|
|
97
|
-
|
|
33
|
+
Xenon sits inside Appium and turns a set of Android and iOS devices, real or virtual, on one machine or many, into a shared lab. Tests ask for a device with ordinary Appium capabilities. Xenon picks a free one the caller is allowed to use, records the session, heals selectors that broke, and shows everything on a live dashboard. People use the same dashboard to watch, control, record and reserve devices.
|
|
34
|
+
|
|
35
|
+
## Contents
|
|
36
|
+
|
|
37
|
+
- [Highlights](#highlights)
|
|
38
|
+
- [Requirements](#requirements)
|
|
39
|
+
- [Quick start](#quick-start)
|
|
40
|
+
- [Hub and nodes](#hub-and-nodes)
|
|
41
|
+
- [Configuration](#configuration)
|
|
42
|
+
- [Capabilities for your tests](#capabilities-for-your-tests)
|
|
43
|
+
- [Self-healing](#self-healing)
|
|
44
|
+
- [Security and access](#security-and-access)
|
|
45
|
+
- [API](#api)
|
|
46
|
+
- [Observability](#observability)
|
|
47
|
+
- [Development](#development)
|
|
48
|
+
- [Upgrading](#upgrading)
|
|
49
|
+
- [Getting help](#getting-help)
|
|
50
|
+
- [Contributing](#contributing)
|
|
51
|
+
- [License](#license)
|
|
52
|
+
|
|
53
|
+
## Highlights
|
|
54
|
+
|
|
55
|
+
**Device lab**
|
|
56
|
+
- Finds Android devices and emulators (adb), iPhones (go-ios) and iOS simulators by itself.
|
|
57
|
+
- Allocates a free, healthy device per session, and queues requests when none is free.
|
|
58
|
+
- One hub, many nodes: every machine's devices form one pool, behind one URL and one set of rules.
|
|
59
|
+
- Teams decide who may use which device; reservations and programmatic leases hold one for a person or a pipeline.
|
|
60
|
+
|
|
61
|
+
**Live control**
|
|
62
|
+
- Live preview of any device in the browser (MJPEG, or hardware H.264 on Android).
|
|
63
|
+
- Tap, swipe, type, press keys, take screenshots, install apps and read the clipboard remotely.
|
|
64
|
+
- Streaming Android logs with filters; a multi-device view that records several phones side by side.
|
|
65
|
+
|
|
66
|
+
**Test evidence**
|
|
67
|
+
- Video, screenshots, device logs and the full command log for every session, grouped by build.
|
|
68
|
+
- CPU and memory charts per session, on both platforms.
|
|
69
|
+
- Network capture with mocks and HAR export (Android), and one-click bug report bundles.
|
|
70
|
+
|
|
71
|
+
**Self-healing**
|
|
72
|
+
- When `findElement` fails, six escalating strategies look for the element, from stored fingerprints to an LLM.
|
|
73
|
+
- The **Selector health** page lists the selectors that needed healing, with a suggested fix to copy into your test.
|
|
74
|
+
|
|
75
|
+
**Built for teams**
|
|
76
|
+
- Users, roles (`SUPER_ADMIN`, `ADMIN`, `MEMBER`), teams and scoped API tokens.
|
|
77
|
+
- Per-user rate limits, single-use tickets for streams and downloads, and a complete [OpenAPI reference](#api).
|
|
78
|
+
|
|
79
|
+
## Requirements
|
|
80
|
+
|
|
81
|
+
| Component | Needed |
|
|
82
|
+
|---|---|
|
|
83
|
+
| **Node.js** | 20.19 or later (what Appium 3 needs) |
|
|
84
|
+
| **Appium** | 3.x (`npm i -g appium`) |
|
|
85
|
+
| **Android** | Android SDK platform tools (`adb`) and the UiAutomator2 driver |
|
|
86
|
+
| **iOS** | macOS with Xcode, [go-ios](https://github.com/danielpaulus/go-ios) and the XCUITest driver |
|
|
87
|
+
| **Database** | SQLite, built in. PostgreSQL is supported for larger hubs. |
|
|
88
|
+
| **Optional** | `ffmpeg` for recordings; an AI provider key (Gemini, OpenAI, Anthropic or a local Ollama) for the AI healing tiers |
|
|
89
|
+
|
|
90
|
+
## Quick start
|
|
91
|
+
|
|
92
|
+
**1. Install the plugin and a driver.**
|
|
98
93
|
|
|
99
94
|
```bash
|
|
100
|
-
# Install Xenon plugin
|
|
101
95
|
appium plugin install --source=npm @xenon-device-management/xenon
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
git clone https://github.com/xenon-platform/xenon.git
|
|
105
|
-
cd xenon
|
|
106
|
-
npm install
|
|
107
|
-
npm run build:all
|
|
108
|
-
appium plugin install --source=local .
|
|
96
|
+
appium driver install uiautomator2 # Android
|
|
97
|
+
appium driver install xcuitest # iOS (macOS only)
|
|
109
98
|
```
|
|
110
99
|
|
|
111
|
-
|
|
100
|
+
**2. Start Appium with Xenon and its dashboard.**
|
|
112
101
|
|
|
113
102
|
```bash
|
|
114
|
-
# Start Appium with Xenon
|
|
115
103
|
appium server --use-plugins=xenon \
|
|
116
104
|
--plugin-xenon-platform=both \
|
|
117
105
|
--plugin-xenon-enable-dashboard
|
|
118
106
|
```
|
|
119
107
|
|
|
120
|
-
|
|
108
|
+
**3. Open the dashboard** at [http://localhost:4723/xenon/](http://localhost:4723/xenon/) and sign in as the first super admin. Unless you set `XENON_BOOTSTRAP_ADMIN_EMAIL` and `XENON_BOOTSTRAP_ADMIN_PASSWORD` before the first start, that is `admin@xenon.local` / `Admin@123`. **Change it at once** on any machine others can reach.
|
|
121
109
|
|
|
122
|
-
|
|
110
|
+
**4. Point a test at it.** Use your access key and an API token; both are under **Profile** in the dashboard, where you create the token:
|
|
123
111
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
112
|
+
```js
|
|
113
|
+
const capabilities = {
|
|
114
|
+
platformName: 'Android',
|
|
115
|
+
'appium:automationName': 'UiAutomator2',
|
|
116
|
+
'xe:options': {
|
|
117
|
+
accessKey: process.env.XENON_ACCESS_KEY,
|
|
118
|
+
token: process.env.XENON_TOKEN,
|
|
119
|
+
},
|
|
120
|
+
'xe:build': 'nightly-2026-10-04',
|
|
121
|
+
'xe:name': 'Checkout: pay by card',
|
|
122
|
+
};
|
|
123
|
+
// WebdriverIO, Appium's Java client, Python client and others all work:
|
|
124
|
+
// connect to http://localhost:4723 with these capabilities.
|
|
136
125
|
```
|
|
137
126
|
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
appium server --config xenon-config.yaml
|
|
141
|
-
```
|
|
127
|
+
The session appears on the dashboard under **Sessions**, with its video and logs once it ends.
|
|
142
128
|
|
|
143
|
-
|
|
129
|
+
## Hub and nodes
|
|
144
130
|
|
|
145
|
-
|
|
131
|
+
A **hub** is the server your tests and people talk to. A **node** is any other machine with devices attached. Every node runs Xenon too and reports its devices to the hub, so the whole lab is one pool behind the hub's URL. Sessions, live control and recordings on a node's device all go through the hub, which applies the team rules and checks access.
|
|
146
132
|
|
|
147
133
|
```bash
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
134
|
+
# On each node
|
|
135
|
+
export XENON_HUB_ACCESS_KEY="xen_..." # a node user's access key, from the hub
|
|
136
|
+
export XENON_HUB_TOKEN="..." # that user's token, with the devices scope
|
|
137
|
+
appium server --use-plugins=xenon \
|
|
138
|
+
--plugin-xenon-platform=both \
|
|
139
|
+
--plugin-xenon-hub=http://hub.example.com:4723
|
|
154
140
|
```
|
|
155
141
|
|
|
156
|
-
|
|
142
|
+
[Node provisioning](docs/node-provisioning.md) covers creating the node's user, its token, and recovering a lost one.
|
|
157
143
|
|
|
158
|
-
|
|
144
|
+
## Configuration
|
|
159
145
|
|
|
160
|
-
Xenon
|
|
146
|
+
Xenon reads its settings from Appium's config file or from `--plugin-xenon-*` flags. A config file suits anything beyond a quick try:
|
|
161
147
|
|
|
162
|
-
| Option | Description | Default |
|
|
163
|
-
|--------|-------------|---------|
|
|
164
|
-
| `buildCleanupDays` | Retention period in days | `30` |
|
|
165
|
-
| `buildCleanupMaxCount` | Maximum number of builds to keep | `100` |
|
|
166
|
-
| `buildCleanupSchedule` | Cron schedule for the cleanup job | `"0 0 * * *"` |
|
|
167
|
-
| `deleteBuildAssets` | Delete video recordings and screenshots from disk | `true` |
|
|
168
|
-
|
|
169
|
-
**Example (YAML):**
|
|
170
148
|
```yaml
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
149
|
+
# xenon.yaml: run with appium server --config xenon.yaml
|
|
150
|
+
server:
|
|
151
|
+
use-plugins: [xenon]
|
|
152
|
+
plugin:
|
|
153
|
+
xenon:
|
|
154
|
+
platform: both # android, ios or both
|
|
155
|
+
enableDashboard: true
|
|
156
|
+
maxSessions: 8 # sessions this server runs at once
|
|
157
|
+
enableSelfHealing: true
|
|
158
|
+
buildCleanupDays: 30 # how long builds, videos and screenshots are kept
|
|
177
159
|
```
|
|
178
160
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
See [docs/server-args.md](docs/server-args.md) for all available options.
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
## 📋 Capabilities
|
|
186
|
-
|
|
187
|
-
Xenon uses the `xe:` prefix for its custom capabilities. You can also use `xenon:` as an alternative.
|
|
188
|
-
|
|
189
|
-
### `xe:options`: credentials, leases and nested options
|
|
161
|
+
Every option, with its default, is in [Server arguments](docs/server-args.md), and [Data retention](docs/retention.md) explains the cleanup job. Lab-wide settings such as health checks, cleanup and the AI provider can also be changed in the dashboard's **Settings**, **AI engine** and **Maintenance** pages; changing them needs a super admin.
|
|
190
162
|
|
|
191
|
-
|
|
163
|
+
### Environment variables
|
|
192
164
|
|
|
193
|
-
|
|
165
|
+
Keep credentials in the environment, not in config files or shell history.
|
|
194
166
|
|
|
195
|
-
|
|
|
196
|
-
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
167
|
+
| Variable | What it does |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `XENON_BOOTSTRAP_ADMIN_EMAIL`, `XENON_BOOTSTRAP_ADMIN_PASSWORD` | The first super admin, created on the hub's first start. |
|
|
170
|
+
| `XENON_AI_PROVIDER` | `gemini`, `openai`, `anthropic` or `ollama`, for the AI healing tiers. |
|
|
171
|
+
| `XENON_GEMINI_API_KEY`, `XENON_OPENAI_API_KEY`, `XENON_ANTHROPIC_API_KEY` | The provider's key. The dashboard never stores or shows keys. |
|
|
172
|
+
| `XENON_AI_MODEL`, `XENON_AI_BASE_URL` | A different model, or a custom endpoint such as a local Ollama. |
|
|
173
|
+
| `XENON_DB_PROVIDER`, `DATABASE_URL` | `sqlite` (default, a file under `~/.cache/xenon`) or `postgresql`, and its URL. |
|
|
174
|
+
| `XENON_AUTO_MIGRATE` | `true` (default) applies database migrations at startup. Set `false` if your pipeline applies them. |
|
|
175
|
+
| `XENON_HUB_ACCESS_KEY`, `XENON_HUB_TOKEN` | On a node: the credentials it uses to talk to its hub. |
|
|
176
|
+
| `XENON_REQUIRE_SESSION_TOKEN` | Refuse sessions created without valid credentials. |
|
|
177
|
+
| `XENON_REQUIRE_COMMAND_AUTH` | Check credentials on every Appium command, not only when the session is created. |
|
|
178
|
+
| `XENON_ALLOWED_ORIGINS` | Extra origins the dashboard may be served from, for a reverse proxy on another host. |
|
|
179
|
+
| `XENON_AUTH_DISABLED` | `true` turns sign-in off. For local development only. |
|
|
199
180
|
|
|
200
|
-
|
|
181
|
+
## Capabilities for your tests
|
|
201
182
|
|
|
202
|
-
|
|
203
|
-
|------------|-------------|---------|
|
|
204
|
-
| `xe:record_video` | Enable video recording | `true` |
|
|
205
|
-
| `xe:screenshot_on_failure` | Capture screenshot on test failure | `true` |
|
|
206
|
-
| `xe:screenshot_on_every_command` | Capture screenshot after each command | `false` |
|
|
207
|
-
| `xe:save_device_logs` | Save device logs (logcat/syslog) | `false` |
|
|
183
|
+
Xenon's own capabilities use the `xe:` prefix. Credentials and other options go in `xe:options`.
|
|
208
184
|
|
|
209
|
-
|
|
185
|
+
| Capability | Purpose |
|
|
186
|
+
|---|---|
|
|
187
|
+
| `xe:options` | `accessKey` and `token`, or a `sessionToken`; a lease's `leaseId` and `leaseToken`; optionally a `team`. Xenon removes the credentials before the driver or any record sees them. |
|
|
188
|
+
| `xe:build`, `xe:name` | Group sessions into a build and name them on the dashboard. |
|
|
189
|
+
| `xe:record_video` | Record the session's video. |
|
|
190
|
+
| `xe:screenshot_on_failure`, `xe:screenshot_on_every_command` | Take screenshots when a command fails, or after every command. |
|
|
191
|
+
| `xe:save_device_logs` | Keep the device's logs with the session. |
|
|
192
|
+
| `appium:udids`, `appium:minSDK`, `appium:maxSDK`, `appium:tags` | Narrow which devices the session may get. |
|
|
193
|
+
| `appium:iPhoneOnly`, `appium:iPadOnly`, `appium:filterByHost` | Limit to iPhone or iPad simulators, or to one node. |
|
|
194
|
+
| `appium:deviceAvailabilityTimeout`, `appium:deviceRetryInterval` | How long to wait for a free device, and how often to look (ms). |
|
|
210
195
|
|
|
211
|
-
|
|
212
|
-
|------------|-------------|---------|
|
|
213
|
-
| `appium:udids` | Comma-separated list of allowed UDIDs | `"device1,device2"` |
|
|
214
|
-
| `appium:minSDK` | Minimum OS version | `"15"` |
|
|
215
|
-
| `appium:maxSDK` | Maximum OS version | `"17"` |
|
|
216
|
-
| `appium:iPhoneOnly` | Use only iPhone simulators | `true` |
|
|
217
|
-
| `appium:iPadOnly` | Use only iPad simulators | `true` |
|
|
218
|
-
| `appium:filterByHost` | Filter by node IP address | `"192.168.0.100"` |
|
|
196
|
+
From inside a test, the `xenon:` execute commands report to the dashboard:
|
|
219
197
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|------------|-------------|---------|
|
|
224
|
-
| `appium:deviceAvailabilityTimeout` | Wait time for device availability (ms) | `180000` |
|
|
225
|
-
| `appium:deviceRetryInterval` | Polling interval for device check (ms) | `10000` |
|
|
226
|
-
|
|
227
|
-
### Example Configuration
|
|
228
|
-
|
|
229
|
-
```javascript
|
|
230
|
-
const capabilities = {
|
|
231
|
-
platformName: 'iOS',
|
|
232
|
-
'appium:automationName': 'XCUITest',
|
|
233
|
-
'appium:app': '/path/to/app.ipa',
|
|
234
|
-
|
|
235
|
-
// Xenon capabilities
|
|
236
|
-
'xe:build': 'Sprint-42',
|
|
237
|
-
'xe:name': 'Login Flow Test',
|
|
238
|
-
'xe:record_video': true,
|
|
239
|
-
'xe:screenshot_on_failure': true,
|
|
240
|
-
'xe:save_device_logs': true,
|
|
241
|
-
|
|
242
|
-
// Device filtering
|
|
243
|
-
'appium:minSDK': '16',
|
|
244
|
-
'appium:iPhoneOnly': true
|
|
245
|
-
};
|
|
198
|
+
```js
|
|
199
|
+
await driver.execute('xenon: setSessionStatus', { status: 'passed', reason: 'All steps OK' });
|
|
200
|
+
await driver.execute('xenon: captureEvidence', { reason: 'Payment confirmed' });
|
|
246
201
|
```
|
|
247
202
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
Xenon supports extended control and reporting via the `xenon:` execute script namespace. These commands allow you to interact with the Xenon dashboard and session management directly from your test code.
|
|
251
|
-
|
|
252
|
-
| Command | Description | Example |
|
|
253
|
-
|---------|-------------|---------|
|
|
254
|
-
| `xenon: setSessionStatus` | Mark session as passed/failed in dashboard | `{"status": "passed", "reason": "All steps OK"}` |
|
|
255
|
-
| `xenon: setSessionName` | Update session name at runtime | `{"name": "Step 2: Payment Verification"}` |
|
|
256
|
-
| `xenon: captureEvidence` | Trigger manual screenshot with custom label | `{"reason": "Checkpoint reached", "label": "success"}` |
|
|
257
|
-
| `xenon: addTag` | Add searchable tags to the session | `{"tag": "regression"}` |
|
|
258
|
-
| `xenon: debug` | Send custom debug logs to Xenon dashboard | `{"message": "API Response: 200 OK"}` |
|
|
259
|
-
|
|
260
|
-
Full reference for these commands is in the Swagger UI at `/xenon/api-docs`.
|
|
261
|
-
|
|
262
|
-
---
|
|
263
|
-
|
|
264
|
-
## 🧠 AI Self-Healing
|
|
265
|
-
|
|
266
|
-
Xenon features a best-in-class, 5-tier self-healing system that ensures your tests never fail due to minor UI changes. It automatically intercepts `NoSuchElementError` and attempts to recover the locator using increasingly advanced methods.
|
|
267
|
-
|
|
268
|
-
### 🛡️ The 5-Tier Strategy
|
|
269
|
-
|
|
270
|
-
| Tier | Provider | Mechanism | Stability |
|
|
271
|
-
|:---|:---|:---|:---|
|
|
272
|
-
| **1** | **Native** | Standard Appium `findElement` | Baseline |
|
|
273
|
-
| **2** | **Fuzzy XML**| **Weighted Signature Matching** (Dice Coefficient) | **85%+** |
|
|
274
|
-
| **3** | **OCR** | Local Text Recognition (Tesseract.js) | High |
|
|
275
|
-
| **4** | **Visual AI**| AI-powered coordinate discovery | High |
|
|
276
|
-
| **5** | **LLM** | Deep Reasoning (Gemini/OpenAI) | Absolute |
|
|
277
|
-
|
|
278
|
-
### ⚡ Signature-Based Learning (Etalon)
|
|
279
|
-
|
|
280
|
-
Xenon "learns" during every successful run. When an element is found, it captures a persistent **Element Signature (Etalon)**.
|
|
281
|
-
- **Zero Configuration**: Learning is fully automatic and backgrounded.
|
|
282
|
-
- **Persistent Memory**: Signatures are stored in your database (LokiJS or PostgreSQL).
|
|
283
|
-
- **Extreme Precision**: Even if `id`, `text`, or `class` changes, Xenon uses anchor attributes (`content-desc`, `resource-id`) from its memory to find the match with industrial-grade confidence.
|
|
284
|
-
|
|
285
|
-
### 🎛️ Control & Transparency
|
|
286
|
-
|
|
287
|
-
Xenon provides full visibility and control over its self-healing system:
|
|
203
|
+
Also available: `setSessionName`, `addTag` and `debug`, and on Android with network capture on, `addMock`, `getRequests` and `exportHar`.
|
|
288
204
|
|
|
289
|
-
|
|
290
|
-
```bash
|
|
291
|
-
appium server --use-plugins=xenon --plugin-xenon-enable-self-healing=true
|
|
292
|
-
```
|
|
293
|
-
- **Live Configuration**: Toggle self-healing directly from the **Xenon Dashboard Settings** at runtime without restarting the server.
|
|
294
|
-
- **Audit Logs**: Every healing event is recorded in the session command history. You can see:
|
|
295
|
-
- **Original Selector**: The locator that failed.
|
|
296
|
-
- **Recovered Selector**: The replacement locator found by Xenon.
|
|
297
|
-
- **Confidence Score**: The mathematical match probability (0-1.0).
|
|
298
|
-
- **Healing Tier**: Which tier (Fuzzy XML, OCR, etc.) performed the recovery.
|
|
205
|
+
For CI, a **lease** reserves a device before the test starts and hands back ready-made capabilities: `POST /xenon/api/sdk/leases`. See the [API reference](#api).
|
|
299
206
|
|
|
300
|
-
|
|
207
|
+
## Self-healing
|
|
301
208
|
|
|
302
|
-
|
|
209
|
+
When `findElement` can't find an element, Xenon tries six strategies in turn, cheapest first, before the test sees a failure:
|
|
303
210
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
### OpenAPI Spec
|
|
314
|
-
|
|
315
|
-
Get the raw OpenAPI specification:
|
|
316
|
-
```
|
|
317
|
-
http://localhost:4723/xenon/api-docs.json
|
|
318
|
-
```
|
|
211
|
+
| Tier | Strategy | How it finds the element |
|
|
212
|
+
|---|---|---|
|
|
213
|
+
| 0 | **Resilio** | Fingerprints of the element stored from earlier successful runs |
|
|
214
|
+
| 1 | **Native** | The original selector, retried |
|
|
215
|
+
| 2 | **Fuzzy XML** | The page source compared with the stored fingerprint |
|
|
216
|
+
| 3 | **OCR** | The element's text read from a screenshot |
|
|
217
|
+
| 4 | **Visual AI** | A screenshot analysed by the configured AI provider |
|
|
218
|
+
| 5 | **LLM** | The page source and the failed selector reasoned about by an LLM |
|
|
319
219
|
|
|
320
|
-
|
|
220
|
+
Before healing, an optional **autowait** retries `findElement` for a while, since most "broken" selectors are slow screens. Turn healing off with `--plugin-xenon-enable-self-healing=false`, or per session with `xe:options.healingTiers`.
|
|
321
221
|
|
|
322
|
-
|
|
323
|
-
|----------|-----------|-------------|
|
|
324
|
-
| **Devices** | `/xenon/api/devices` | Device discovery and management |
|
|
325
|
-
| **Sessions** | `/xenon/api/session` | Session management and logs |
|
|
326
|
-
| **Builds** | `/xenon/api/build` | Build and test execution tracking |
|
|
327
|
-
| **Control** | `/xenon/api/control` | Interactive device control |
|
|
328
|
-
| **Reservations** | `/xenon/api/reservation` | Device reservation for exclusive use |
|
|
329
|
-
| **Applications** | `/xenon/api/apps` | App repository and installation |
|
|
330
|
-
| **Webhooks** | `/xenon/api/webhook` | Notification webhook configuration |
|
|
222
|
+
The dashboard's **Selector health** page lists every selector that needed healing in a period, how often and in which sessions, with a suggested fix to copy in JavaScript, Java, Python, C# or Ruby. Mark one as fixed and Xenon watches later runs to confirm it: it moves from **To fix** to **Being verified** to **Fixed**, and back to **To fix** if it breaks again. **Muted** hides a selector you've decided to leave.
|
|
331
223
|
|
|
332
|
-
|
|
224
|
+
## Security and access
|
|
333
225
|
|
|
334
|
-
|
|
335
|
-
```bash
|
|
336
|
-
# Get all devices
|
|
337
|
-
GET /xenon/api/devices
|
|
226
|
+
Every `/xenon/api` request needs a credential:
|
|
338
227
|
|
|
339
|
-
|
|
340
|
-
|
|
228
|
+
| Credential | How to send it | Use it for |
|
|
229
|
+
|---|---|---|
|
|
230
|
+
| Dashboard session | The cookie `POST /xenon/api/auth/login` sets | People, in the browser |
|
|
231
|
+
| Access key and token | `x-xenon-access-key` and `x-xenon-token` headers | CI, scripts, nodes |
|
|
232
|
+
| Bearer token | `Authorization: Bearer <jwt>`, from `POST /xenon/api/auth/token` | SDKs, MCP tools, short-lived access |
|
|
341
233
|
|
|
342
|
-
|
|
343
|
-
POST /xenon/api/device/{udid}/block
|
|
344
|
-
POST /xenon/api/device/{udid}/unblock
|
|
345
|
-
```
|
|
234
|
+
**Roles** decide what a person may do; a token's **scopes** narrow it further, and a token can never have more than the credential that created it.
|
|
346
235
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
236
|
+
| Scope | Allows |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `read` | Reading devices, sessions, builds, apps and logs |
|
|
239
|
+
| `sessions` | Running Appium sessions and acting on selectors |
|
|
240
|
+
| `devices` | Controlling devices, previews, recordings, reservations and leases |
|
|
241
|
+
| `admin` | Users, teams, API keys, webhooks and lab settings (with the matching role) |
|
|
351
242
|
|
|
352
|
-
|
|
353
|
-
POST /xenon/api/control/{udid}/tap
|
|
354
|
-
{ "x": 100, "y": 200 }
|
|
243
|
+
**Teams** decide which devices someone can reach: a member sees their teams' devices and the shared pool, and everything else answers as if it didn't exist. [Teams](docs/teams.md) explains setting them up.
|
|
355
244
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
245
|
+
For a lab others can reach, we recommend:
|
|
246
|
+
- set your own bootstrap admin password before the first start;
|
|
247
|
+
- turn on `XENON_REQUIRE_SESSION_TOKEN`, so every session has an owner, and `XENON_REQUIRE_COMMAND_AUTH` on the hub;
|
|
248
|
+
- serve Xenon over HTTPS, and keep nodes on a trusted network.
|
|
359
249
|
|
|
360
|
-
|
|
361
|
-
POST /xenon/api/control/{udid}/text
|
|
362
|
-
{ "text": "Hello World" }
|
|
250
|
+
## API
|
|
363
251
|
|
|
364
|
-
|
|
365
|
-
POST /xenon/api/control/{udid}/shell
|
|
366
|
-
{ "command": "pm list packages" }
|
|
252
|
+
Every endpoint is documented in the OpenAPI reference that each server serves:
|
|
367
253
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
```
|
|
254
|
+
- **Interactive reference:** `http://<your-host>:4723/xenon/api-docs`
|
|
255
|
+
- **Raw OpenAPI document:** `http://<your-host>:4723/xenon/api-docs.json`
|
|
371
256
|
|
|
372
|
-
#### Reservations
|
|
373
257
|
```bash
|
|
374
|
-
#
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
# Release reservation
|
|
379
|
-
DELETE /xenon/api/reservation/{udid}/{host}
|
|
258
|
+
# List the devices you can see
|
|
259
|
+
curl -H "x-xenon-access-key: $XENON_ACCESS_KEY" -H "x-xenon-token: $XENON_TOKEN" \
|
|
260
|
+
http://localhost:4723/xenon/api/devices
|
|
380
261
|
|
|
381
|
-
#
|
|
382
|
-
POST /
|
|
383
|
-
|
|
262
|
+
# Lease an Android device for 30 minutes
|
|
263
|
+
curl -X POST -H "Content-Type: application/json" \
|
|
264
|
+
-H "x-xenon-access-key: $XENON_ACCESS_KEY" -H "x-xenon-token: $XENON_TOKEN" \
|
|
265
|
+
-d '{"filters":{"platform":"android"},"durationMs":1800000}' \
|
|
266
|
+
http://localhost:4723/xenon/api/sdk/leases
|
|
384
267
|
```
|
|
385
268
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
## 🎨 Dashboard
|
|
389
|
-
|
|
390
|
-
Access the dashboard at `http://localhost:4723/xenon/`
|
|
269
|
+
Errors are JSON with an `error` field. Requests are rate limited per API key or per user; a limited answer carries `X-RateLimit-*` headers, and `429` with `Retry-After` past the budget.
|
|
391
270
|
|
|
392
|
-
|
|
393
|
-
<img src="assets/dashboard.png" alt="Xenon Dashboard" width="100%">
|
|
394
|
-
</p>
|
|
395
|
-
|
|
396
|
-
### Views
|
|
397
|
-
|
|
398
|
-
| View | Description |
|
|
399
|
-
|------|-------------|
|
|
400
|
-
| **Devices** | Real-time device grid with status indicators |
|
|
401
|
-
| **Sessions** | Active and historical session management |
|
|
402
|
-
| **Builds** | Test runs grouped by build identifier |
|
|
403
|
-
| **Control** | Interactive device control interface |
|
|
404
|
-
|
|
405
|
-
---
|
|
271
|
+
## Observability
|
|
406
272
|
|
|
407
|
-
|
|
273
|
+
Xenon emits OpenTelemetry traces and logs for every session and command. [`examples/observability`](examples/observability) has a ready Docker Compose stack (Grafana, Tempo and Loki) to view them.
|
|
408
274
|
|
|
409
|
-
|
|
410
|
-
**[https://xenon-docs.vercel.app/](https://xenon-docs.vercel.app/)**
|
|
411
|
-
|
|
412
|
-
### Quick Links
|
|
413
|
-
- [Server arguments & env vars](docs/server-args.md)
|
|
414
|
-
- [Node provisioning](docs/node-provisioning.md) — pair-auth credentials for hub-node deployments
|
|
415
|
-
- [Teams & device access](docs/teams.md)
|
|
416
|
-
- [Data retention & cleanup](docs/retention.md)
|
|
417
|
-
- API reference: live Swagger at `/xenon/api-docs`
|
|
418
|
-
|
|
419
|
-
---
|
|
420
|
-
|
|
421
|
-
## 🏗️ Development
|
|
275
|
+
## Development
|
|
422
276
|
|
|
423
277
|
```bash
|
|
424
|
-
|
|
425
|
-
git clone https://github.com/xenon-platform/xenon.git
|
|
278
|
+
git clone https://github.com/Rabindra184/xenon.git
|
|
426
279
|
cd xenon
|
|
427
280
|
npm install
|
|
428
|
-
|
|
429
|
-
# Build everything (Plugin + Dashboard)
|
|
430
|
-
npm run build:all
|
|
431
|
-
|
|
432
|
-
# High-velocity development loop
|
|
433
|
-
# (Auto-rebuilds and restarts Appium server)
|
|
434
|
-
npm run dev
|
|
435
|
-
|
|
436
|
-
# Run tests
|
|
437
|
-
npm run test:all # Unit tests
|
|
438
|
-
npm run test:android # Android integration
|
|
439
|
-
npm run test:ios # iOS integration
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
---
|
|
443
|
-
|
|
444
|
-
## 🔐 Authentication
|
|
445
|
-
|
|
446
|
-
All `/xenon/api/*` endpoints are authenticated. Xenon supports three shapes — pick whichever matches your caller.
|
|
447
|
-
|
|
448
|
-
### Identity model
|
|
449
|
-
|
|
450
|
-
Xenon ships an enterprise identity stack: **users** with roles (`SUPER_ADMIN` / `ADMIN` / `MEMBER`), **teams** that scope which devices a user can reach, and **API tokens** minted per-user with their own scope set. The dashboard, programmatic clients, and hub-node channel all flow through the same identity.
|
|
451
|
-
|
|
452
|
-
### Auth shapes
|
|
453
|
-
|
|
454
|
-
| Shape | Header(s) | When to use |
|
|
455
|
-
|---|---|---|
|
|
456
|
-
| **Cookie session** | `Cookie: xenon_dashboard_session=…` | Dashboard browser sessions. Set by `POST /api/auth/login` with `{email, password}`. |
|
|
457
|
-
| **Pair auth** | `X-Xenon-Access-Key` + `X-Xenon-Token` | Programmatic clients (CI, SDK, hub→node). Each user has one access key (rotatable) and any number of scoped tokens. |
|
|
458
|
-
| **Auth disabled** | _(none)_ | Local dev only. Set `--plugin-xenon-auth-disabled` (or `XENON_AUTH_DISABLED=true`). A WARN logs every 60 s. |
|
|
459
|
-
|
|
460
|
-
### First-run bootstrap
|
|
461
|
-
|
|
462
|
-
On first start Xenon creates a `SUPER_ADMIN` user from these env vars (defaults `admin@xenon.local` / `Admin@123`):
|
|
463
|
-
|
|
464
|
-
```bash
|
|
465
|
-
export XENON_BOOTSTRAP_ADMIN_EMAIL="you@example.com"
|
|
466
|
-
export XENON_BOOTSTRAP_ADMIN_PASSWORD="..." # change me
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
Sign in at `https://<host>/xenon/` with these credentials. From `/profile` you can mint API tokens and rotate your access key. For CI use, programmatically `POST /api/auth/login` to get the cookie, then `POST /api/profile/tokens` to mint a scoped token.
|
|
470
|
-
|
|
471
|
-
### Generating a programmatic token
|
|
472
|
-
|
|
473
|
-
```bash
|
|
474
|
-
# 1. Get your access key + a fresh token from /profile in the dashboard, or:
|
|
475
|
-
curl -s -X POST -b "xenon_dashboard_session=$COOKIE" \
|
|
476
|
-
-H 'Content-Type: application/json' \
|
|
477
|
-
-d '{"name":"ci","scopes":["sessions","read"]}' \
|
|
478
|
-
http://localhost:4723/xenon/api/profile/tokens
|
|
479
|
-
|
|
480
|
-
# 2. Use it on every subsequent call:
|
|
481
|
-
curl -H "X-Xenon-Access-Key: xen_..." -H "X-Xenon-Token: ..." \
|
|
482
|
-
http://localhost:4723/xenon/api/devices
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
### Scopes
|
|
486
|
-
|
|
487
|
-
Tokens carry one or more scopes; the user's role controls which scopes they can grant.
|
|
488
|
-
|
|
489
|
-
| Scope | Access |
|
|
490
|
-
|-------|--------|
|
|
491
|
-
| `read` | GET sessions, devices, logs, apps |
|
|
492
|
-
| `sessions` | Create/delete sessions and reservations |
|
|
493
|
-
| `devices` | Block/unblock devices, install apps, hub-node `/register` and `/unblock` |
|
|
494
|
-
| `admin` | User / team / API-key management, webhooks |
|
|
495
|
-
|
|
496
|
-
### Teams (device access control)
|
|
497
|
-
|
|
498
|
-
Scopes govern *which verbs* a token can call; **teams** govern *which devices* it can reach. A user bound to a team sees the team's devices plus the shared pool (`teamId = null`). `admin`-scope tokens bypass team filtering.
|
|
499
|
-
|
|
500
|
-
Test clients authenticate a session with the access key and a token (with the `sessions` scope) in `xe:options`, Xenon's capability namespace:
|
|
501
|
-
|
|
502
|
-
```js
|
|
503
|
-
const caps = {
|
|
504
|
-
platformName: 'iOS',
|
|
505
|
-
'appium:automationName': 'XCUITest',
|
|
506
|
-
'xe:options': {
|
|
507
|
-
accessKey: process.env.XENON_ACCESS_KEY, // user with team membership
|
|
508
|
-
token: process.env.XENON_TOKEN,
|
|
509
|
-
// optional: team: '<team-id>' to pin allocation to one of your teams
|
|
510
|
-
// (any team for an admin)
|
|
511
|
-
},
|
|
512
|
-
};
|
|
281
|
+
npm run dev # migrate the database, build, install the plugin and start Appium
|
|
513
282
|
```
|
|
514
283
|
|
|
515
|
-
|
|
284
|
+
| Command | Does |
|
|
285
|
+
|---|---|
|
|
286
|
+
| `npm run build:all` | Build the plugin and the dashboard |
|
|
287
|
+
| `npm run test:all` | Run the unit tests |
|
|
288
|
+
| `npm run test:android`, `npm run test:ios` | Run the integration tests on real devices |
|
|
289
|
+
| `npm run db:generate -- --name <change>` | Add a database migration after editing `prisma/schema.prisma` |
|
|
516
290
|
|
|
517
|
-
|
|
291
|
+
The dashboard is a React app in [`web/`](web), and the documentation site is in [`website/`](website).
|
|
518
292
|
|
|
519
|
-
|
|
293
|
+
## Upgrading
|
|
520
294
|
|
|
521
|
-
|
|
295
|
+
Read the [changelog](CHANGELOG.md) before upgrading: each release says whether it brings a database migration and what behaves differently. Then update the plugin and restart Appium:
|
|
522
296
|
|
|
523
297
|
```bash
|
|
524
|
-
|
|
525
|
-
export XENON_HUB_TOKEN="..."
|
|
298
|
+
appium plugin update xenon
|
|
526
299
|
```
|
|
527
300
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
---
|
|
531
|
-
|
|
532
|
-
## 🌱 Environment Variables
|
|
301
|
+
Migrations are applied when Xenon starts. If you set `XENON_AUTO_MIGRATE=false`, apply them yourself first; from a source checkout, that is `npm run db:migrate`.
|
|
533
302
|
|
|
534
|
-
|
|
303
|
+
## Getting help
|
|
535
304
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
| `XENON_AI_MODEL` | Override the default model for the selected provider. |
|
|
540
|
-
| `XENON_AI_BASE_URL` | Custom base URL (local Ollama, OpenAI-compatible gateway). |
|
|
541
|
-
| `XENON_GEMINI_API_KEY` / `GEMINI_API_KEY` | Gemini credentials. `XENON_`-prefixed form wins if both set. |
|
|
542
|
-
| `XENON_OPENAI_API_KEY` / `OPENAI_API_KEY` | OpenAI credentials. |
|
|
543
|
-
| `XENON_ANTHROPIC_API_KEY` / `ANTHROPIC_API_KEY` | Anthropic credentials. |
|
|
544
|
-
| `XENON_OPENAI_MODEL` | Alternate way to set the OpenAI model. |
|
|
545
|
-
| `XENON_OTEL_DEBUG` | When `true`, OpenTelemetry adds a ConsoleSpanExporter so every span is logged. Dev/tracing only. |
|
|
546
|
-
| `XENON_DB_PROVIDER` | `sqlite` or `postgresql`. Same as `--plugin-xenon-databaseProvider`. |
|
|
547
|
-
| `DATABASE_URL` | Prisma database URL. Falls back to `file:~/.cache/xenon/xenon.db`. |
|
|
548
|
-
| `XENON_HUB_ACCESS_KEY` | Node→hub outbound: access key the node sends in `X-Xenon-Access-Key`. Required alongside `XENON_HUB_TOKEN`. See [docs/node-provisioning.md](docs/node-provisioning.md). |
|
|
549
|
-
| `XENON_HUB_TOKEN` | Node→hub outbound: token the node sends in `X-Xenon-Token`. Required alongside `XENON_HUB_ACCESS_KEY`. |
|
|
550
|
-
| `XENON_BOOTSTRAP_ADMIN_EMAIL` / `XENON_BOOTSTRAP_ADMIN_PASSWORD` | First-run super-admin user, created on first hub boot. Defaults `admin@xenon.local` / `Admin@123`. Change in any non-throwaway environment. |
|
|
551
|
-
| `XENON_AUTH_DISABLED` | `true` to disable all auth. Local dev only. |
|
|
552
|
-
| `XENON_AUTO_MIGRATE` | When `true` (default), the hub auto-applies pending schema changes at startup (`prisma db push` for SQLite, `prisma migrate deploy` for PostgreSQL). Set `false` if you manage migrations externally via CI for auditable change-control. |
|
|
305
|
+
- **Questions and setup:** the [documentation](https://xenon-6e6.pages.dev), then the API reference on your own server.
|
|
306
|
+
- **Bugs and ideas:** [open an issue](https://github.com/Rabindra184/xenon/issues/new/choose).
|
|
307
|
+
- **Security problems:** report them privately, as [SECURITY.md](SECURITY.md) explains; please don't open a public issue.
|
|
553
308
|
|
|
554
|
-
|
|
309
|
+
## Contributing
|
|
555
310
|
|
|
556
|
-
|
|
311
|
+
Issues and pull requests are welcome. [CONTRIBUTING.md](CONTRIBUTING.md) covers the development setup, tests, database changes and what a pull request needs. Everyone taking part is expected to follow the [code of conduct](CODE_OF_CONDUCT.md). To refresh the screenshots above after a dashboard change, build `web/` and run `node scripts/dev/readme-screenshots.js`.
|
|
557
312
|
|
|
558
|
-
##
|
|
313
|
+
## License
|
|
559
314
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
### Contributors
|
|
563
|
-
|
|
564
|
-
<a href="https://github.com/xenon-platform/xenon/graphs/contributors">
|
|
565
|
-
<img src="https://contrib.rocks/image?repo=xenon-platform/xenon" />
|
|
566
|
-
</a>
|
|
567
|
-
|
|
568
|
-
---
|
|
569
|
-
|
|
570
|
-
## 📜 License
|
|
571
|
-
|
|
572
|
-
ISC License - See [LICENSE](LICENSE) for details.
|
|
573
|
-
|
|
574
|
-
---
|
|
575
|
-
|
|
576
|
-
<p align="center">
|
|
577
|
-
<strong>Xenon</strong> - Stable. Reliable. Intelligent.
|
|
578
|
-
<br>
|
|
579
|
-
<em>Named after Element 54 - the noble gas known for stability</em>
|
|
580
|
-
</p>
|
|
315
|
+
Xenon is released under the [ISC License](LICENSE).
|