@xenon-device-management/xenon 2.12.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/package.json +2 -2
- package/lib/public/assets/{AnnotationOverlay-Crkn72_3.js → AnnotationOverlay-CzbFYtPI.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-BThVlj_j.js → ApiKeyGate-CA5KFHHJ.js} +1 -1
- package/lib/public/assets/{BugReportButton-Dwa0GnBh.js → BugReportButton-D-DSGg8C.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-Ccc-pOls.js → DeviceMosaicView-paDEFU4B.js} +1 -1
- package/lib/public/assets/{EmptyState-BeuTrlN_.js → EmptyState-BE_5f8Vw.js} +1 -1
- package/lib/public/assets/{FieldGroup-DBIaNxl1.js → FieldGroup-DqMf7Rf3.js} +1 -1
- package/lib/public/assets/{FilterMenu-BgSAS6iT.js → FilterMenu-BQY75Su3.js} +1 -1
- package/lib/public/assets/{Menu-GCbP6gYN.js → Menu-BiW3os9u.js} +1 -1
- package/lib/public/assets/{Modal-ByESeBRC.js → Modal-DfawTxih.js} +1 -1
- package/lib/public/assets/{RecordingPage-Bwyf3WMl.js → RecordingPage-BvemyRiy.js} +1 -1
- package/lib/public/assets/{RecordingsPage-7kM1rB3i.js → RecordingsPage-CXfw7wpd.js} +1 -1
- package/lib/public/assets/{SegmentedControl-B8tTU8kD.js → SegmentedControl-B7m9kpom.js} +1 -1
- package/lib/public/assets/{SettingCard-Clg_Ijbc.js → SettingCard-Cw6lDD0l.js} +1 -1
- package/lib/public/assets/{Table-CIaR0QWV.js → Table-DlEuFgv-.js} +1 -1
- package/lib/public/assets/{activity-Bengh9SD.js → activity-CnLvNwae.js} +1 -1
- package/lib/public/assets/{ai-settings-G9SymiPH.js → ai-settings-D7Te8o-a.js} +1 -1
- package/lib/public/assets/{api-keys-CrGlbfn-.js → api-keys-DphSVzBr.js} +1 -1
- package/lib/public/assets/{apps-DIbcGVlU.js → apps-vUfu15lX.js} +1 -1
- package/lib/public/assets/{arrow-left-BtJIXXMY.js → arrow-left-CLQWAiCY.js} +1 -1
- package/lib/public/assets/{arrow-right-zm-NagQh.js → arrow-right-K6A5e_Ee.js} +1 -1
- package/lib/public/assets/{arrow-up-right-DdU8YEYp.js → arrow-up-right-DUA_lq6f.js} +1 -1
- package/lib/public/assets/{auth-shell-CHmBFmOz.js → auth-shell-BYRHcZd6.js} +2 -2
- package/lib/public/assets/{builds-page-JkzWKRpU.js → builds-page-C0fTvIG8.js} +1 -1
- package/lib/public/assets/{button-CqhPBGRj.js → button-BxnHCCsX.js} +1 -1
- package/lib/public/assets/{calendar-C7eYYJTo.js → calendar-CqVsn28A.js} +1 -1
- package/lib/public/assets/{check-DpbIM4E0.js → check-R6V3bot0.js} +1 -1
- package/lib/public/assets/{chevron-right-Dl4X1PRz.js → chevron-right-BA2Fntu-.js} +1 -1
- package/lib/public/assets/{circle-check-Du3sjbfV.js → circle-check-D4EzyGzs.js} +1 -1
- package/lib/public/assets/{circle-x-BSSqhpXi.js → circle-x-Cf6AF2B2.js} +1 -1
- package/lib/public/assets/{clock-DMT60v1C.js → clock-EZfet0sS.js} +1 -1
- package/lib/public/assets/{copy-BKNyOehd.js → copy-Bc6P8v9V.js} +1 -1
- package/lib/public/assets/{device-explorer-CoPR8DW3.js → device-explorer-BFXpRq6d.js} +1 -1
- package/lib/public/assets/{download-BM6Xn22t.js → download-Bd8OGLzi.js} +1 -1
- package/lib/public/assets/{forgot-password-B8WqMqBT.js → forgot-password-CoE7Ugtx.js} +1 -1
- package/lib/public/assets/{index-DauQh6ie.js → index-BiL0Enl_.js} +1 -1
- package/lib/public/assets/{index-ClrpAMAT.js → index-CHOr4JCs.js} +2 -2
- package/lib/public/assets/{input-CyKdLnEx.js → input-CJ8z3IO5.js} +1 -1
- package/lib/public/assets/{line-chart-EIBXwYGo.js → line-chart-CPK0ObjV.js} +1 -1
- package/lib/public/assets/{list-checks-fWsgD9bI.js → list-checks-mXGHUVbB.js} +1 -1
- package/lib/public/assets/{lock-CVCe56TH.js → lock-BSXM1xvq.js} +1 -1
- package/lib/public/assets/{login-BJ8a7yVD.js → login-Ct2iW066.js} +1 -1
- package/lib/public/assets/{maintenance-settings-DcRmTLS6.js → maintenance-settings-DaOs00P6.js} +1 -1
- package/lib/public/assets/{monitor-Bw1YQZnL.js → monitor-D8WQK8md.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-C01jbqkO.js → mouse-pointer-2-Cw2tWzDX.js} +1 -1
- package/lib/public/assets/{network-CBGUjJDJ.js → network-Ba_uFxWo.js} +1 -1
- package/lib/public/assets/{overview-Cxe8aQ7C.js → overview-CxrZ7_6k.js} +1 -1
- package/lib/public/assets/{page-header-B92DKLiq.js → page-header-Cm1XL49f.js} +1 -1
- package/lib/public/assets/{play-Ck0L-0_m.js → play-C0CuKLXj.js} +1 -1
- package/lib/public/assets/{plus-Dq3tCy2N.js → plus-CIIvR_6Z.js} +1 -1
- package/lib/public/assets/{profile-page--dkDiKbr.js → profile-page-BZ_e-Bgw.js} +1 -1
- package/lib/public/assets/{recording-group-store-5BYIFFN9.js → recording-group-store-BWeeNs2_.js} +1 -1
- package/lib/public/assets/{reset-password-KYWlfjia.js → reset-password-lfNz8Rm1.js} +1 -1
- package/lib/public/assets/{runbook-page-mnsxgS1d.js → runbook-page-Cnec3VSx.js} +1 -1
- package/lib/public/assets/{select-BBOIZTYm.js → select-CZuCnold.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-CcI2j_Su.js → selector-detail-redirect-BG3pxFLw.js} +1 -1
- package/lib/public/assets/{selector-health-page-BdJb_x5L.js → selector-health-page-DsyL58tb.js} +1 -1
- package/lib/public/assets/{session-detail-page-BjLaJy8z.js → session-detail-page-ZZyGM413.js} +1 -1
- package/lib/public/assets/{settings-B6cpMhsx.js → settings-DGZ7B_AR.js} +1 -1
- package/lib/public/assets/{stat-tile-BU9e4s36.js → stat-tile-DFhv8JoI.js} +1 -1
- package/lib/public/assets/{tablet-hgbEwrWq.js → tablet-68J5f6zh.js} +1 -1
- package/lib/public/assets/{teams-uiiG4hZM.js → teams-eHSyEpbD.js} +1 -1
- package/lib/public/assets/{trash-2-NK_Iazmg.js → trash-2-CcSsv4fS.js} +1 -1
- package/lib/public/assets/{upload-BUX8TNFi.js → upload-BYf26K71.js} +1 -1
- package/lib/public/assets/{use-builds-data-DZszmFyl.js → use-builds-data-CHW-MhaP.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-BK4B9kRC.js → use-password-reset-mode-DozShXkk.js} +1 -1
- package/lib/public/assets/{users-BU4XBbMT.js → users-BpYnpOl2.js} +1 -1
- package/lib/public/assets/{users-BDh1xjad.js → users-CcSQRoK9.js} +1 -1
- package/lib/public/assets/{video-off-BOjNwT4R.js → video-off-dFKgps73.js} +1 -1
- package/lib/public/assets/{webhook-settings-BMzyUcx5.js → webhook-settings-DD1XjiOD.js} +1 -1
- package/lib/public/assets/{zap-DTWwVMUg.js → zap-COC2tZaH.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 +32 -32
- 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/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/lib/src/app/swagger-docs.js +0 -1701
|
@@ -0,0 +1,2295 @@
|
|
|
1
|
+
paths:
|
|
2
|
+
/api/devices:
|
|
3
|
+
get:
|
|
4
|
+
operationId: listGridDevices
|
|
5
|
+
summary: List the devices in the lab
|
|
6
|
+
description: |
|
|
7
|
+
Every device this server knows: its own phones and, on a hub, the
|
|
8
|
+
phones its nodes report. Each row carries `teamName`, the name of
|
|
9
|
+
its team (null for the shared pool).
|
|
10
|
+
|
|
11
|
+
Needs role `MEMBER` or above; no scope is checked. A member sees the
|
|
12
|
+
shared pool and their teams' devices only. An admin, or any caller
|
|
13
|
+
on a server with auth disabled, sees every device.
|
|
14
|
+
|
|
15
|
+
With `sessionId`, the answer is the one visible device whose
|
|
16
|
+
`session_id` is that value, as an object, or an empty `200` body
|
|
17
|
+
when there is none. When the Appium dashboard plugin is installed,
|
|
18
|
+
each row in the list also gets `dashboard_link` and
|
|
19
|
+
`total_session_count`.
|
|
20
|
+
|
|
21
|
+
`GET /api/device` is the same endpoint.
|
|
22
|
+
tags:
|
|
23
|
+
- Devices
|
|
24
|
+
parameters:
|
|
25
|
+
- $ref: '#/components/parameters/GridSessionIdQuery'
|
|
26
|
+
responses:
|
|
27
|
+
'200':
|
|
28
|
+
$ref: '#/components/responses/GridDeviceListOk'
|
|
29
|
+
'401':
|
|
30
|
+
$ref: '#/components/responses/Unauthorized'
|
|
31
|
+
'403':
|
|
32
|
+
$ref: '#/components/responses/Forbidden'
|
|
33
|
+
'429':
|
|
34
|
+
$ref: '#/components/responses/RateLimited'
|
|
35
|
+
/api/device:
|
|
36
|
+
get:
|
|
37
|
+
operationId: listGridDevicesAlias
|
|
38
|
+
summary: List the devices in the lab (alias)
|
|
39
|
+
description: |
|
|
40
|
+
The same as `GET /api/devices`: every device the caller may see,
|
|
41
|
+
with `teamName`, or with `sessionId` the one device running that
|
|
42
|
+
session. Needs role `MEMBER` or above. A member sees the shared pool
|
|
43
|
+
and their teams' devices only.
|
|
44
|
+
tags:
|
|
45
|
+
- Devices
|
|
46
|
+
parameters:
|
|
47
|
+
- $ref: '#/components/parameters/GridSessionIdQuery'
|
|
48
|
+
responses:
|
|
49
|
+
'200':
|
|
50
|
+
$ref: '#/components/responses/GridDeviceListOk'
|
|
51
|
+
'401':
|
|
52
|
+
$ref: '#/components/responses/Unauthorized'
|
|
53
|
+
'403':
|
|
54
|
+
$ref: '#/components/responses/Forbidden'
|
|
55
|
+
'429':
|
|
56
|
+
$ref: '#/components/responses/RateLimited'
|
|
57
|
+
/api/device/{platform}:
|
|
58
|
+
get:
|
|
59
|
+
operationId: listGridDevicesByPlatform
|
|
60
|
+
summary: List the devices of one platform
|
|
61
|
+
description: |
|
|
62
|
+
The devices of one platform, filtered like `GET /api/devices`: a
|
|
63
|
+
member sees the shared pool and their teams' devices only. Each row
|
|
64
|
+
carries `teamName`. Needs role `MEMBER` or above.
|
|
65
|
+
|
|
66
|
+
`platform` is matched case-insensitively. Any other value than
|
|
67
|
+
`ios` or `android` answers `200` with an empty list, not an error.
|
|
68
|
+
`booted` keeps only devices whose `state` is `Booted` (simulators):
|
|
69
|
+
any value turns it on, `booted=false` included.
|
|
70
|
+
tags:
|
|
71
|
+
- Devices
|
|
72
|
+
parameters:
|
|
73
|
+
- in: path
|
|
74
|
+
name: platform
|
|
75
|
+
required: true
|
|
76
|
+
description: The platform, `ios` or `android` (any letter case).
|
|
77
|
+
schema:
|
|
78
|
+
type: string
|
|
79
|
+
example: android
|
|
80
|
+
- in: query
|
|
81
|
+
name: deviceType
|
|
82
|
+
required: false
|
|
83
|
+
description: Keep only devices of this type.
|
|
84
|
+
schema:
|
|
85
|
+
type: string
|
|
86
|
+
enum:
|
|
87
|
+
- real
|
|
88
|
+
- simulator
|
|
89
|
+
- emulator
|
|
90
|
+
- in: query
|
|
91
|
+
name: booted
|
|
92
|
+
required: false
|
|
93
|
+
description: Present (with any value) to keep only devices whose `state` is `Booted`.
|
|
94
|
+
schema:
|
|
95
|
+
type: string
|
|
96
|
+
example: 'true'
|
|
97
|
+
responses:
|
|
98
|
+
'200':
|
|
99
|
+
description: The matching devices; empty for an unknown platform.
|
|
100
|
+
content:
|
|
101
|
+
application/json:
|
|
102
|
+
schema:
|
|
103
|
+
type: array
|
|
104
|
+
items:
|
|
105
|
+
$ref: '#/components/schemas/GridDevice'
|
|
106
|
+
example:
|
|
107
|
+
- udid: emulator-5554
|
|
108
|
+
name: Pixel 7 API 34
|
|
109
|
+
platform: android
|
|
110
|
+
host: http://192.168.1.100:4723
|
|
111
|
+
nodeId: 6b1e4c2a-8f3d-4a9e-b7c5-2d0f1e8a9b3c
|
|
112
|
+
busy: false
|
|
113
|
+
session_id: null
|
|
114
|
+
state: device
|
|
115
|
+
sdk: '14'
|
|
116
|
+
deviceType: emulator
|
|
117
|
+
realDevice: false
|
|
118
|
+
offline: false
|
|
119
|
+
userBlocked: false
|
|
120
|
+
teamId: null
|
|
121
|
+
teamName: null
|
|
122
|
+
tags:
|
|
123
|
+
- lab-row-3
|
|
124
|
+
reservedBy: null
|
|
125
|
+
reservedByUserId: null
|
|
126
|
+
reservedUntil: null
|
|
127
|
+
healthStatus: Healthy
|
|
128
|
+
batteryLevel: 100
|
|
129
|
+
screenWidth: '1080'
|
|
130
|
+
screenHeight: '2400'
|
|
131
|
+
'401':
|
|
132
|
+
$ref: '#/components/responses/Unauthorized'
|
|
133
|
+
'403':
|
|
134
|
+
$ref: '#/components/responses/Forbidden'
|
|
135
|
+
'429':
|
|
136
|
+
$ref: '#/components/responses/RateLimited'
|
|
137
|
+
/api/block:
|
|
138
|
+
post:
|
|
139
|
+
operationId: blockGridDevice
|
|
140
|
+
summary: Block a device for maintenance
|
|
141
|
+
description: |
|
|
142
|
+
Sets `userBlocked` on a device, so no new session or lease is
|
|
143
|
+
allocated to it. A session already running on it is not affected,
|
|
144
|
+
and neither is `busy`.
|
|
145
|
+
|
|
146
|
+
Send both `udid` and `host`, exactly as `GET /api/devices` shows
|
|
147
|
+
them: the device is the row of that udid on that host. Without
|
|
148
|
+
either, `400`; with no such row, `404`. Through 2.12 the body was a
|
|
149
|
+
device filter, so an empty body blocked the first device in the
|
|
150
|
+
list, and a udid alone either row of a udid two nodes report
|
|
151
|
+
(emulators).
|
|
152
|
+
|
|
153
|
+
Needs role `ADMIN` and the `devices` scope.
|
|
154
|
+
tags:
|
|
155
|
+
- Devices
|
|
156
|
+
requestBody:
|
|
157
|
+
required: true
|
|
158
|
+
content:
|
|
159
|
+
application/json:
|
|
160
|
+
schema:
|
|
161
|
+
$ref: '#/components/schemas/GridDeviceSelector'
|
|
162
|
+
example:
|
|
163
|
+
udid: emulator-5554
|
|
164
|
+
host: http://192.168.1.100:4723
|
|
165
|
+
responses:
|
|
166
|
+
'200':
|
|
167
|
+
description: The device is blocked.
|
|
168
|
+
content:
|
|
169
|
+
application/json:
|
|
170
|
+
schema:
|
|
171
|
+
$ref: '#/components/schemas/Success'
|
|
172
|
+
example:
|
|
173
|
+
success: true
|
|
174
|
+
'400':
|
|
175
|
+
$ref: '#/components/responses/GridDeviceSelectorMissing'
|
|
176
|
+
'401':
|
|
177
|
+
$ref: '#/components/responses/Unauthorized'
|
|
178
|
+
'403':
|
|
179
|
+
$ref: '#/components/responses/Forbidden'
|
|
180
|
+
'404':
|
|
181
|
+
$ref: '#/components/responses/GridDeviceNotFound'
|
|
182
|
+
'429':
|
|
183
|
+
$ref: '#/components/responses/RateLimited'
|
|
184
|
+
'500':
|
|
185
|
+
description: The device could not be updated.
|
|
186
|
+
content:
|
|
187
|
+
application/json:
|
|
188
|
+
schema:
|
|
189
|
+
$ref: '#/components/schemas/Error'
|
|
190
|
+
example:
|
|
191
|
+
success: false
|
|
192
|
+
error: Failed to block device
|
|
193
|
+
/api/unblock:
|
|
194
|
+
post:
|
|
195
|
+
operationId: unblockGridDevice
|
|
196
|
+
summary: Unblock a device
|
|
197
|
+
description: |
|
|
198
|
+
Clears `userBlocked`, so the device can be allocated again. Send
|
|
199
|
+
both `udid` and `host`, as for `POST /api/block`: without either,
|
|
200
|
+
`400`; with no such row, `404`.
|
|
201
|
+
|
|
202
|
+
Needs role `ADMIN` and the `devices` scope.
|
|
203
|
+
tags:
|
|
204
|
+
- Devices
|
|
205
|
+
requestBody:
|
|
206
|
+
required: true
|
|
207
|
+
content:
|
|
208
|
+
application/json:
|
|
209
|
+
schema:
|
|
210
|
+
$ref: '#/components/schemas/GridDeviceSelector'
|
|
211
|
+
example:
|
|
212
|
+
udid: 00008110-00084CE80E51401E
|
|
213
|
+
host: http://192.168.1.101:4723
|
|
214
|
+
responses:
|
|
215
|
+
'200':
|
|
216
|
+
description: The device is unblocked.
|
|
217
|
+
content:
|
|
218
|
+
application/json:
|
|
219
|
+
schema:
|
|
220
|
+
$ref: '#/components/schemas/Success'
|
|
221
|
+
example:
|
|
222
|
+
success: true
|
|
223
|
+
'400':
|
|
224
|
+
$ref: '#/components/responses/GridDeviceSelectorMissing'
|
|
225
|
+
'401':
|
|
226
|
+
$ref: '#/components/responses/Unauthorized'
|
|
227
|
+
'403':
|
|
228
|
+
$ref: '#/components/responses/Forbidden'
|
|
229
|
+
'404':
|
|
230
|
+
$ref: '#/components/responses/GridDeviceNotFound'
|
|
231
|
+
'429':
|
|
232
|
+
$ref: '#/components/responses/RateLimited'
|
|
233
|
+
'500':
|
|
234
|
+
description: The device could not be updated.
|
|
235
|
+
content:
|
|
236
|
+
application/json:
|
|
237
|
+
schema:
|
|
238
|
+
$ref: '#/components/schemas/Error'
|
|
239
|
+
example:
|
|
240
|
+
success: false
|
|
241
|
+
error: Failed to unblock device
|
|
242
|
+
/api/device/tags:
|
|
243
|
+
post:
|
|
244
|
+
operationId: setGridDeviceTags
|
|
245
|
+
summary: Replace a device's tags
|
|
246
|
+
description: |
|
|
247
|
+
Replaces the device's tag list with `tags` (it doesn't append). Tags
|
|
248
|
+
are free-form labels, such as `flaky` or `lab-row-3`, that a lease
|
|
249
|
+
can filter on. The row is chosen by exact `udid` and `host`; a pair
|
|
250
|
+
that names no device changes nothing and still answers `200`.
|
|
251
|
+
|
|
252
|
+
Needs role `ADMIN` and the `devices` scope.
|
|
253
|
+
tags:
|
|
254
|
+
- Devices
|
|
255
|
+
requestBody:
|
|
256
|
+
required: true
|
|
257
|
+
content:
|
|
258
|
+
application/json:
|
|
259
|
+
schema:
|
|
260
|
+
type: object
|
|
261
|
+
required:
|
|
262
|
+
- udid
|
|
263
|
+
- host
|
|
264
|
+
- tags
|
|
265
|
+
properties:
|
|
266
|
+
udid:
|
|
267
|
+
type: string
|
|
268
|
+
example: emulator-5554
|
|
269
|
+
host:
|
|
270
|
+
type: string
|
|
271
|
+
description: The device's host, exactly as `GET /api/devices` shows it.
|
|
272
|
+
example: http://192.168.1.100:4723
|
|
273
|
+
tags:
|
|
274
|
+
type: array
|
|
275
|
+
items:
|
|
276
|
+
type: string
|
|
277
|
+
example:
|
|
278
|
+
- flaky
|
|
279
|
+
- lab-row-3
|
|
280
|
+
responses:
|
|
281
|
+
'200':
|
|
282
|
+
description: The tags were written.
|
|
283
|
+
content:
|
|
284
|
+
application/json:
|
|
285
|
+
schema:
|
|
286
|
+
$ref: '#/components/schemas/Success'
|
|
287
|
+
example:
|
|
288
|
+
success: true
|
|
289
|
+
'400':
|
|
290
|
+
description: '`udid` or `host` is missing, or `tags` is not an array.'
|
|
291
|
+
content:
|
|
292
|
+
application/json:
|
|
293
|
+
schema:
|
|
294
|
+
$ref: '#/components/schemas/Error'
|
|
295
|
+
example:
|
|
296
|
+
error: Missing udid, host, or tags array
|
|
297
|
+
'401':
|
|
298
|
+
$ref: '#/components/responses/Unauthorized'
|
|
299
|
+
'403':
|
|
300
|
+
$ref: '#/components/responses/Forbidden'
|
|
301
|
+
'429':
|
|
302
|
+
$ref: '#/components/responses/RateLimited'
|
|
303
|
+
/api/device/{udid}/team:
|
|
304
|
+
put:
|
|
305
|
+
operationId: assignGridDeviceTeam
|
|
306
|
+
summary: Assign a device to a team
|
|
307
|
+
description: |
|
|
308
|
+
Gives the device to a team, or with `teamId: null` returns it to the
|
|
309
|
+
shared pool, which every member sees. A missing `teamId` counts as
|
|
310
|
+
null. Every row with this udid changes (on a hub, each node that
|
|
311
|
+
reports it), and allocation and live dashboard events follow the new
|
|
312
|
+
team at once.
|
|
313
|
+
|
|
314
|
+
Needs role `ADMIN` and the `admin` scope.
|
|
315
|
+
tags:
|
|
316
|
+
- Devices
|
|
317
|
+
parameters:
|
|
318
|
+
- in: path
|
|
319
|
+
name: udid
|
|
320
|
+
required: true
|
|
321
|
+
description: The device's udid.
|
|
322
|
+
schema:
|
|
323
|
+
type: string
|
|
324
|
+
example: 00008110-00084CE80E51401E
|
|
325
|
+
requestBody:
|
|
326
|
+
required: true
|
|
327
|
+
content:
|
|
328
|
+
application/json:
|
|
329
|
+
schema:
|
|
330
|
+
type: object
|
|
331
|
+
properties:
|
|
332
|
+
teamId:
|
|
333
|
+
type: string
|
|
334
|
+
nullable: true
|
|
335
|
+
description: The team's id, or null for the shared pool.
|
|
336
|
+
example: clx9k2v0a0000qz8r4h7m3n2p
|
|
337
|
+
responses:
|
|
338
|
+
'200':
|
|
339
|
+
description: The device's team changed. `updated` is how many rows changed.
|
|
340
|
+
content:
|
|
341
|
+
application/json:
|
|
342
|
+
schema:
|
|
343
|
+
type: object
|
|
344
|
+
properties:
|
|
345
|
+
ok:
|
|
346
|
+
type: boolean
|
|
347
|
+
updated:
|
|
348
|
+
type: integer
|
|
349
|
+
example:
|
|
350
|
+
ok: true
|
|
351
|
+
updated: 1
|
|
352
|
+
'401':
|
|
353
|
+
$ref: '#/components/responses/Unauthorized'
|
|
354
|
+
'403':
|
|
355
|
+
$ref: '#/components/responses/Forbidden'
|
|
356
|
+
'404':
|
|
357
|
+
description: The team or the device doesn't exist.
|
|
358
|
+
content:
|
|
359
|
+
application/json:
|
|
360
|
+
schema:
|
|
361
|
+
$ref: '#/components/schemas/Error'
|
|
362
|
+
examples:
|
|
363
|
+
team:
|
|
364
|
+
value:
|
|
365
|
+
error: team not found
|
|
366
|
+
device:
|
|
367
|
+
value:
|
|
368
|
+
error: device not found
|
|
369
|
+
'429':
|
|
370
|
+
$ref: '#/components/responses/RateLimited'
|
|
371
|
+
/api/reservation:
|
|
372
|
+
get:
|
|
373
|
+
operationId: listGridReservations
|
|
374
|
+
summary: List the current reservations
|
|
375
|
+
description: |
|
|
376
|
+
The devices reserved right now (an expired reservation isn't
|
|
377
|
+
listed), with the time left. A member sees only reservations on the
|
|
378
|
+
shared pool and their teams' devices; an admin sees all.
|
|
379
|
+
|
|
380
|
+
Needs role `MEMBER` or above. Reservations are deprecated in favour
|
|
381
|
+
of leases (`POST /api/sdk/leases`): every answer carries
|
|
382
|
+
`Deprecation`, `Sunset` and `Link` headers.
|
|
383
|
+
tags:
|
|
384
|
+
- Reservations
|
|
385
|
+
responses:
|
|
386
|
+
'200':
|
|
387
|
+
description: The current reservations.
|
|
388
|
+
headers:
|
|
389
|
+
Deprecation:
|
|
390
|
+
$ref: '#/components/headers/GridDeprecation'
|
|
391
|
+
Sunset:
|
|
392
|
+
$ref: '#/components/headers/GridSunset'
|
|
393
|
+
Link:
|
|
394
|
+
$ref: '#/components/headers/GridLink'
|
|
395
|
+
content:
|
|
396
|
+
application/json:
|
|
397
|
+
schema:
|
|
398
|
+
type: object
|
|
399
|
+
properties:
|
|
400
|
+
success:
|
|
401
|
+
type: boolean
|
|
402
|
+
reservations:
|
|
403
|
+
type: array
|
|
404
|
+
items:
|
|
405
|
+
$ref: '#/components/schemas/GridReservation'
|
|
406
|
+
example:
|
|
407
|
+
success: true
|
|
408
|
+
reservations:
|
|
409
|
+
- udid: emulator-5554
|
|
410
|
+
host: http://192.168.1.100:4723
|
|
411
|
+
name: Pixel 7 API 34
|
|
412
|
+
platform: android
|
|
413
|
+
reservedBy: priya
|
|
414
|
+
reservedUntil: 1791118800000
|
|
415
|
+
reservationReason: Manual checkout regression
|
|
416
|
+
remainingMs: 5400000
|
|
417
|
+
'401':
|
|
418
|
+
$ref: '#/components/responses/Unauthorized'
|
|
419
|
+
'403':
|
|
420
|
+
$ref: '#/components/responses/Forbidden'
|
|
421
|
+
'429':
|
|
422
|
+
$ref: '#/components/responses/RateLimited'
|
|
423
|
+
'500':
|
|
424
|
+
$ref: '#/components/responses/GridReservationFailed'
|
|
425
|
+
post:
|
|
426
|
+
operationId: createGridReservation
|
|
427
|
+
summary: Reserve a device
|
|
428
|
+
description: |
|
|
429
|
+
Marks a device as reserved by `reservedBy` until now plus
|
|
430
|
+
`duration`. `reservedBy` is the name shown on the device; the
|
|
431
|
+
reservation also records the signed-in user who took it
|
|
432
|
+
(`reservedByUserId`), who alone, or an admin, may release or extend
|
|
433
|
+
it. A device already reserved by someone else, or running an Appium
|
|
434
|
+
session, is refused with `409`. The same user under the same
|
|
435
|
+
`reservedBy` may reserve it again, which restarts the clock.
|
|
436
|
+
|
|
437
|
+
The device is the row of `udid` on `host`, so send both exactly as
|
|
438
|
+
`GET /api/devices` shows them. No such row, or a device outside the
|
|
439
|
+
caller's teams, answers `404`, as an unknown one.
|
|
440
|
+
|
|
441
|
+
Needs role `MEMBER` or above and the `devices` scope. Deprecated in
|
|
442
|
+
favour of leases: every answer carries `Deprecation`, `Sunset` and
|
|
443
|
+
`Link` headers.
|
|
444
|
+
tags:
|
|
445
|
+
- Reservations
|
|
446
|
+
requestBody:
|
|
447
|
+
required: true
|
|
448
|
+
content:
|
|
449
|
+
application/json:
|
|
450
|
+
schema:
|
|
451
|
+
type: object
|
|
452
|
+
required:
|
|
453
|
+
- udid
|
|
454
|
+
- host
|
|
455
|
+
- reservedBy
|
|
456
|
+
- duration
|
|
457
|
+
properties:
|
|
458
|
+
udid:
|
|
459
|
+
type: string
|
|
460
|
+
example: emulator-5554
|
|
461
|
+
host:
|
|
462
|
+
type: string
|
|
463
|
+
example: http://192.168.1.100:4723
|
|
464
|
+
reservedBy:
|
|
465
|
+
type: string
|
|
466
|
+
description: Who the reservation is for, as free text.
|
|
467
|
+
example: priya
|
|
468
|
+
duration:
|
|
469
|
+
$ref: '#/components/schemas/GridReservationDuration'
|
|
470
|
+
reason:
|
|
471
|
+
type: string
|
|
472
|
+
example: Manual checkout regression
|
|
473
|
+
example:
|
|
474
|
+
udid: emulator-5554
|
|
475
|
+
host: http://192.168.1.100:4723
|
|
476
|
+
reservedBy: priya
|
|
477
|
+
duration: 2h
|
|
478
|
+
reason: Manual checkout regression
|
|
479
|
+
responses:
|
|
480
|
+
'200':
|
|
481
|
+
description: The device is reserved.
|
|
482
|
+
headers:
|
|
483
|
+
Deprecation:
|
|
484
|
+
$ref: '#/components/headers/GridDeprecation'
|
|
485
|
+
Sunset:
|
|
486
|
+
$ref: '#/components/headers/GridSunset'
|
|
487
|
+
Link:
|
|
488
|
+
$ref: '#/components/headers/GridLink'
|
|
489
|
+
content:
|
|
490
|
+
application/json:
|
|
491
|
+
schema:
|
|
492
|
+
type: object
|
|
493
|
+
properties:
|
|
494
|
+
success:
|
|
495
|
+
type: boolean
|
|
496
|
+
message:
|
|
497
|
+
type: string
|
|
498
|
+
reservation:
|
|
499
|
+
type: object
|
|
500
|
+
properties:
|
|
501
|
+
udid:
|
|
502
|
+
type: string
|
|
503
|
+
host:
|
|
504
|
+
type: string
|
|
505
|
+
reservedBy:
|
|
506
|
+
type: string
|
|
507
|
+
reservedUntil:
|
|
508
|
+
type: integer
|
|
509
|
+
description: Epoch milliseconds.
|
|
510
|
+
reservationReason:
|
|
511
|
+
type: string
|
|
512
|
+
expiresAt:
|
|
513
|
+
type: string
|
|
514
|
+
format: date-time
|
|
515
|
+
example:
|
|
516
|
+
success: true
|
|
517
|
+
message: Device emulator-5554 reserved for priya
|
|
518
|
+
reservation:
|
|
519
|
+
udid: emulator-5554
|
|
520
|
+
host: http://192.168.1.100:4723
|
|
521
|
+
reservedBy: priya
|
|
522
|
+
reservedUntil: 1791118800000
|
|
523
|
+
reservationReason: Manual checkout regression
|
|
524
|
+
expiresAt: '2026-10-04T15:00:00.000Z'
|
|
525
|
+
'400':
|
|
526
|
+
description: A field is missing, or `duration` is invalid or outside 1 minute to 24 hours.
|
|
527
|
+
content:
|
|
528
|
+
application/json:
|
|
529
|
+
schema:
|
|
530
|
+
$ref: '#/components/schemas/Error'
|
|
531
|
+
examples:
|
|
532
|
+
missing:
|
|
533
|
+
value:
|
|
534
|
+
success: false
|
|
535
|
+
error: 'Missing required fields: udid, host, reservedBy, duration'
|
|
536
|
+
duration:
|
|
537
|
+
value:
|
|
538
|
+
success: false
|
|
539
|
+
error: duration must be between 60000ms (1 minute) and 86400000ms (24 hours)
|
|
540
|
+
'401':
|
|
541
|
+
$ref: '#/components/responses/Unauthorized'
|
|
542
|
+
'403':
|
|
543
|
+
$ref: '#/components/responses/Forbidden'
|
|
544
|
+
'404':
|
|
545
|
+
$ref: '#/components/responses/GridDeviceNotFound'
|
|
546
|
+
'409':
|
|
547
|
+
description: Reserved by someone else, or running an Appium session.
|
|
548
|
+
content:
|
|
549
|
+
application/json:
|
|
550
|
+
schema:
|
|
551
|
+
$ref: '#/components/schemas/Error'
|
|
552
|
+
examples:
|
|
553
|
+
reserved:
|
|
554
|
+
value:
|
|
555
|
+
success: false
|
|
556
|
+
error: Device is already reserved by marco until 2026-10-04T15:00:00.000Z
|
|
557
|
+
busy:
|
|
558
|
+
value:
|
|
559
|
+
success: false
|
|
560
|
+
error: Device is currently busy with an Appium session. Cannot reserve.
|
|
561
|
+
'429':
|
|
562
|
+
$ref: '#/components/responses/RateLimited'
|
|
563
|
+
'500':
|
|
564
|
+
$ref: '#/components/responses/GridReservationFailed'
|
|
565
|
+
/api/reservation/{udid}/{host}:
|
|
566
|
+
delete:
|
|
567
|
+
operationId: releaseGridReservation
|
|
568
|
+
summary: Release a reservation
|
|
569
|
+
description: |
|
|
570
|
+
Ends the device's reservation at once. Only the user who took it,
|
|
571
|
+
or an admin, may: anyone else gets `403 not_reservation_holder`. A
|
|
572
|
+
reservation taken before 2.13 records no user, and anyone who may
|
|
573
|
+
reserve may still release it. A device outside the caller's teams
|
|
574
|
+
answers `404`.
|
|
575
|
+
|
|
576
|
+
`host` is URL-encoded (`http%3A%2F%2F192.168.1.100%3A4723`). The
|
|
577
|
+
device is the row of `udid` on that `host`; no such row answers
|
|
578
|
+
`404`.
|
|
579
|
+
|
|
580
|
+
Needs role `MEMBER` or above and the `devices` scope. Deprecated in
|
|
581
|
+
favour of leases.
|
|
582
|
+
tags:
|
|
583
|
+
- Reservations
|
|
584
|
+
parameters:
|
|
585
|
+
- $ref: '#/components/parameters/GridReservationUdid'
|
|
586
|
+
- $ref: '#/components/parameters/GridReservationHost'
|
|
587
|
+
responses:
|
|
588
|
+
'200':
|
|
589
|
+
description: The reservation is released.
|
|
590
|
+
headers:
|
|
591
|
+
Deprecation:
|
|
592
|
+
$ref: '#/components/headers/GridDeprecation'
|
|
593
|
+
Sunset:
|
|
594
|
+
$ref: '#/components/headers/GridSunset'
|
|
595
|
+
Link:
|
|
596
|
+
$ref: '#/components/headers/GridLink'
|
|
597
|
+
content:
|
|
598
|
+
application/json:
|
|
599
|
+
schema:
|
|
600
|
+
type: object
|
|
601
|
+
properties:
|
|
602
|
+
success:
|
|
603
|
+
type: boolean
|
|
604
|
+
message:
|
|
605
|
+
type: string
|
|
606
|
+
example:
|
|
607
|
+
success: true
|
|
608
|
+
message: Reservation released for device emulator-5554
|
|
609
|
+
'400':
|
|
610
|
+
$ref: '#/components/responses/GridNotReserved'
|
|
611
|
+
'401':
|
|
612
|
+
$ref: '#/components/responses/Unauthorized'
|
|
613
|
+
'403':
|
|
614
|
+
description: >-
|
|
615
|
+
The caller isn't the user who took the reservation, nor an admin; or lacks the
|
|
616
|
+
role or scope.
|
|
617
|
+
content:
|
|
618
|
+
application/json:
|
|
619
|
+
schema:
|
|
620
|
+
$ref: '#/components/schemas/Error'
|
|
621
|
+
examples:
|
|
622
|
+
notHolder:
|
|
623
|
+
summary: Someone else's reservation
|
|
624
|
+
value:
|
|
625
|
+
success: false
|
|
626
|
+
error: not_reservation_holder
|
|
627
|
+
message: >-
|
|
628
|
+
Only the person who reserved this device, or an admin, can change the
|
|
629
|
+
reservation.
|
|
630
|
+
scope:
|
|
631
|
+
summary: Role or scope missing
|
|
632
|
+
value:
|
|
633
|
+
error: insufficient scope
|
|
634
|
+
'404':
|
|
635
|
+
$ref: '#/components/responses/GridDeviceNotFound'
|
|
636
|
+
'429':
|
|
637
|
+
$ref: '#/components/responses/RateLimited'
|
|
638
|
+
'500':
|
|
639
|
+
$ref: '#/components/responses/GridReservationFailed'
|
|
640
|
+
/api/reservation/{udid}/{host}/extend:
|
|
641
|
+
post:
|
|
642
|
+
operationId: extendGridReservation
|
|
643
|
+
summary: Extend a reservation
|
|
644
|
+
description: |
|
|
645
|
+
Adds `duration` (at least 1 minute) to the current reservation's
|
|
646
|
+
end, keeping who holds it, its `reservedBy` and its reason. As with
|
|
647
|
+
a new reservation, it may end at most 24 hours from now. Only the
|
|
648
|
+
user who took it, or an admin, may extend it, as for releasing. A
|
|
649
|
+
device outside the caller's teams answers `404`.
|
|
650
|
+
|
|
651
|
+
`host` is URL-encoded, as for releasing. Needs role `MEMBER` or
|
|
652
|
+
above and the `devices` scope. Deprecated in favour of leases.
|
|
653
|
+
tags:
|
|
654
|
+
- Reservations
|
|
655
|
+
parameters:
|
|
656
|
+
- $ref: '#/components/parameters/GridReservationUdid'
|
|
657
|
+
- $ref: '#/components/parameters/GridReservationHost'
|
|
658
|
+
requestBody:
|
|
659
|
+
required: true
|
|
660
|
+
content:
|
|
661
|
+
application/json:
|
|
662
|
+
schema:
|
|
663
|
+
type: object
|
|
664
|
+
required:
|
|
665
|
+
- duration
|
|
666
|
+
properties:
|
|
667
|
+
duration:
|
|
668
|
+
$ref: '#/components/schemas/GridReservationDuration'
|
|
669
|
+
example:
|
|
670
|
+
duration: 1h
|
|
671
|
+
responses:
|
|
672
|
+
'200':
|
|
673
|
+
description: The reservation is extended.
|
|
674
|
+
headers:
|
|
675
|
+
Deprecation:
|
|
676
|
+
$ref: '#/components/headers/GridDeprecation'
|
|
677
|
+
Sunset:
|
|
678
|
+
$ref: '#/components/headers/GridSunset'
|
|
679
|
+
Link:
|
|
680
|
+
$ref: '#/components/headers/GridLink'
|
|
681
|
+
content:
|
|
682
|
+
application/json:
|
|
683
|
+
schema:
|
|
684
|
+
type: object
|
|
685
|
+
properties:
|
|
686
|
+
success:
|
|
687
|
+
type: boolean
|
|
688
|
+
message:
|
|
689
|
+
type: string
|
|
690
|
+
newExpiresAt:
|
|
691
|
+
type: string
|
|
692
|
+
format: date-time
|
|
693
|
+
example:
|
|
694
|
+
success: true
|
|
695
|
+
message: Reservation extended for device emulator-5554
|
|
696
|
+
newExpiresAt: '2026-10-04T16:00:00.000Z'
|
|
697
|
+
'400':
|
|
698
|
+
description: >-
|
|
699
|
+
The device isn't reserved, or `duration` is invalid, under a minute, or would end the
|
|
700
|
+
reservation more than 24 hours from now.
|
|
701
|
+
content:
|
|
702
|
+
application/json:
|
|
703
|
+
schema:
|
|
704
|
+
$ref: '#/components/schemas/Error'
|
|
705
|
+
examples:
|
|
706
|
+
notReserved:
|
|
707
|
+
value:
|
|
708
|
+
success: false
|
|
709
|
+
error: Device is not reserved
|
|
710
|
+
duration:
|
|
711
|
+
value:
|
|
712
|
+
success: false
|
|
713
|
+
error: 'Invalid duration. Use: 1h, 2h, 4h, 8h or a number in milliseconds'
|
|
714
|
+
tooShort:
|
|
715
|
+
value:
|
|
716
|
+
success: false
|
|
717
|
+
error: duration must be at least 60000ms (1 minute)
|
|
718
|
+
tooLong:
|
|
719
|
+
value:
|
|
720
|
+
success: false
|
|
721
|
+
error: A reservation can end at most 24 hours from now
|
|
722
|
+
'401':
|
|
723
|
+
$ref: '#/components/responses/Unauthorized'
|
|
724
|
+
'403':
|
|
725
|
+
description: >-
|
|
726
|
+
The caller isn't the user who took the reservation, nor an admin; or lacks the
|
|
727
|
+
role or scope.
|
|
728
|
+
content:
|
|
729
|
+
application/json:
|
|
730
|
+
schema:
|
|
731
|
+
$ref: '#/components/schemas/Error'
|
|
732
|
+
examples:
|
|
733
|
+
notHolder:
|
|
734
|
+
summary: Someone else's reservation
|
|
735
|
+
value:
|
|
736
|
+
success: false
|
|
737
|
+
error: not_reservation_holder
|
|
738
|
+
message: >-
|
|
739
|
+
Only the person who reserved this device, or an admin, can change the
|
|
740
|
+
reservation.
|
|
741
|
+
scope:
|
|
742
|
+
summary: Role or scope missing
|
|
743
|
+
value:
|
|
744
|
+
error: insufficient scope
|
|
745
|
+
'404':
|
|
746
|
+
$ref: '#/components/responses/GridDeviceNotFound'
|
|
747
|
+
'429':
|
|
748
|
+
$ref: '#/components/responses/RateLimited'
|
|
749
|
+
'500':
|
|
750
|
+
$ref: '#/components/responses/GridReservationFailed'
|
|
751
|
+
/api/sdk/leases:
|
|
752
|
+
post:
|
|
753
|
+
operationId: createGridLease
|
|
754
|
+
summary: Lease a device
|
|
755
|
+
description: |
|
|
756
|
+
Claims a free device that matches `filters` for `durationMs`, for a
|
|
757
|
+
programmatic client such as an SDK or MCP tool. The device is marked
|
|
758
|
+
busy, ports for the Appium driver are reserved on the server it is
|
|
759
|
+
attached to, and the answer carries `appiumCapabilities`: pass them
|
|
760
|
+
to `POST /session` unchanged. They include `xe:options.leaseId` and
|
|
761
|
+
`xe:options.leaseToken`, which prove the session holds the lease.
|
|
762
|
+
|
|
763
|
+
`leaseToken` is shown only in this answer and is never stored in
|
|
764
|
+
clear. Keep it: heartbeat, extend and release need it in the
|
|
765
|
+
`x-xenon-lease-token` header.
|
|
766
|
+
|
|
767
|
+
The lease ends after three missed heartbeats (every
|
|
768
|
+
`heartbeatSeconds`) or at `expiresAt`, whichever comes first; a
|
|
769
|
+
sweep runs every 30 s. Ending it frees the device unless a session
|
|
770
|
+
still holds it.
|
|
771
|
+
|
|
772
|
+
Matching: among the shared pool and the caller's teams (an admin is
|
|
773
|
+
unscoped), the devices that match every filter. `filters.platform`
|
|
774
|
+
is lowercase. `udid`, `deviceType`, `tags` (all must be present),
|
|
775
|
+
`platformVersion` (or `sdk`, the same thing), `minSDK`, `maxSDK`
|
|
776
|
+
and `deviceName` (the device's name, ignoring case) narrow it.
|
|
777
|
+
`404` means no device visible to you matches. Of those that match,
|
|
778
|
+
the lease takes one that is free, online, not already leased, not
|
|
779
|
+
blocked, not reserved, and not failing its health check, as a new
|
|
780
|
+
session would; `409` means some match but none of them is. Through
|
|
781
|
+
2.12 `sdk` and `deviceName` were ignored, blocked, reserved and
|
|
782
|
+
unhealthy devices could be leased, and `404` or `409` depended on
|
|
783
|
+
the platform alone.
|
|
784
|
+
|
|
785
|
+
Needs role `MEMBER` or above and the `devices` scope.
|
|
786
|
+
tags:
|
|
787
|
+
- Leases
|
|
788
|
+
requestBody:
|
|
789
|
+
required: true
|
|
790
|
+
content:
|
|
791
|
+
application/json:
|
|
792
|
+
schema:
|
|
793
|
+
$ref: '#/components/schemas/GridLeaseCreateRequest'
|
|
794
|
+
example:
|
|
795
|
+
filters:
|
|
796
|
+
platform: android
|
|
797
|
+
tags:
|
|
798
|
+
- lab-row-3
|
|
799
|
+
durationMs: 1800000
|
|
800
|
+
heartbeatSeconds: 30
|
|
801
|
+
reason: nightly checkout suite
|
|
802
|
+
buildId: build-2026-10-04-nightly
|
|
803
|
+
responses:
|
|
804
|
+
'201':
|
|
805
|
+
description: The lease. Keep `leaseToken`; it is not shown again.
|
|
806
|
+
content:
|
|
807
|
+
application/json:
|
|
808
|
+
schema:
|
|
809
|
+
$ref: '#/components/schemas/GridLease'
|
|
810
|
+
example:
|
|
811
|
+
leaseId: clxa1b2c30000s8k2f9d1e7aa
|
|
812
|
+
leaseToken: 7f3c9a1e5b2d48f0a6c4e8b1d3f5a7c9e2b4d6f8a0c1e3b5d7f9a2c4e6b8d0f1
|
|
813
|
+
device:
|
|
814
|
+
udid: emulator-5554
|
|
815
|
+
host: http://192.168.1.100:4723
|
|
816
|
+
platform: android
|
|
817
|
+
sdk: '14'
|
|
818
|
+
name: Pixel 7 API 34
|
|
819
|
+
screen:
|
|
820
|
+
width: '1080'
|
|
821
|
+
height: '2400'
|
|
822
|
+
realDevice: false
|
|
823
|
+
expiresAt: 1791113400000
|
|
824
|
+
heartbeatSeconds: 30
|
|
825
|
+
allocatedPorts:
|
|
826
|
+
systemPort: 52311
|
|
827
|
+
chromedriverPort: 52312
|
|
828
|
+
mjpegServerPort: 52313
|
|
829
|
+
appiumCapabilities:
|
|
830
|
+
platformName: Android
|
|
831
|
+
appium:automationName: UiAutomator2
|
|
832
|
+
appium:udid: emulator-5554
|
|
833
|
+
appium:newCommandTimeout: 120
|
|
834
|
+
appium:deviceName: Pixel 7 API 34
|
|
835
|
+
appium:platformVersion: '14'
|
|
836
|
+
appium:systemPort: 52311
|
|
837
|
+
appium:chromedriverPort: 52312
|
|
838
|
+
appium:mjpegServerPort: 52313
|
|
839
|
+
xe:options:
|
|
840
|
+
leaseId: clxa1b2c30000s8k2f9d1e7aa
|
|
841
|
+
buildId: build-2026-10-04-nightly
|
|
842
|
+
leaseToken: 7f3c9a1e5b2d48f0a6c4e8b1d3f5a7c9e2b4d6f8a0c1e3b5d7f9a2c4e6b8d0f1
|
|
843
|
+
'400':
|
|
844
|
+
description: '`filters.platform` is missing.'
|
|
845
|
+
content:
|
|
846
|
+
application/json:
|
|
847
|
+
schema:
|
|
848
|
+
$ref: '#/components/schemas/Error'
|
|
849
|
+
example:
|
|
850
|
+
error: bad_request
|
|
851
|
+
details: filters.platform required
|
|
852
|
+
'401':
|
|
853
|
+
$ref: '#/components/responses/Unauthorized'
|
|
854
|
+
'403':
|
|
855
|
+
$ref: '#/components/responses/Forbidden'
|
|
856
|
+
'404':
|
|
857
|
+
description: No device the caller can see matches the filters.
|
|
858
|
+
content:
|
|
859
|
+
application/json:
|
|
860
|
+
schema:
|
|
861
|
+
$ref: '#/components/schemas/Error'
|
|
862
|
+
example:
|
|
863
|
+
error: no_matching_device
|
|
864
|
+
message: No device matches the filters
|
|
865
|
+
'409':
|
|
866
|
+
description: >-
|
|
867
|
+
Devices match, but none is free: busy, leased, blocked, reserved or unhealthy. Retry
|
|
868
|
+
after `retryAfterMs`.
|
|
869
|
+
content:
|
|
870
|
+
application/json:
|
|
871
|
+
schema:
|
|
872
|
+
$ref: '#/components/schemas/Error'
|
|
873
|
+
example:
|
|
874
|
+
error: all_matching_busy
|
|
875
|
+
retryAfterMs: 2000
|
|
876
|
+
'429':
|
|
877
|
+
$ref: '#/components/responses/RateLimited'
|
|
878
|
+
'500':
|
|
879
|
+
description: The lease could not be recorded.
|
|
880
|
+
content:
|
|
881
|
+
application/json:
|
|
882
|
+
schema:
|
|
883
|
+
$ref: '#/components/schemas/Error'
|
|
884
|
+
example:
|
|
885
|
+
error: internal
|
|
886
|
+
message: Unique constraint failed on the fields
|
|
887
|
+
'503':
|
|
888
|
+
description: |
|
|
889
|
+
A device was found but its ports could not be reserved: its server
|
|
890
|
+
didn't answer, or this server has no credentials for it
|
|
891
|
+
(`XENON_HUB_ACCESS_KEY` and `XENON_HUB_TOKEN`, needed whenever auth
|
|
892
|
+
is enabled). The device is freed again.
|
|
893
|
+
content:
|
|
894
|
+
application/json:
|
|
895
|
+
schema:
|
|
896
|
+
$ref: '#/components/schemas/Error'
|
|
897
|
+
example:
|
|
898
|
+
error: device_unhealthy
|
|
899
|
+
details: 'port allocation failed: connect ECONNREFUSED 192.168.1.100:4723'
|
|
900
|
+
/api/sdk/leases/{id}/heartbeat:
|
|
901
|
+
post:
|
|
902
|
+
operationId: heartbeatGridLease
|
|
903
|
+
summary: Keep a lease alive
|
|
904
|
+
description: |
|
|
905
|
+
Records a heartbeat, so the lease isn't ended for missed heartbeats.
|
|
906
|
+
It does not move `expiresAt`; use extend for that. Needs the lease
|
|
907
|
+
token in `x-xenon-lease-token`, role `MEMBER` or above and the
|
|
908
|
+
`devices` scope.
|
|
909
|
+
|
|
910
|
+
An unknown id answers like a wrong token (`403 token_mismatch`), so
|
|
911
|
+
lease ids can't be probed. A lease that has ended or passed
|
|
912
|
+
`expiresAt` answers `410` to its token's holder.
|
|
913
|
+
tags:
|
|
914
|
+
- Leases
|
|
915
|
+
parameters:
|
|
916
|
+
- $ref: '#/components/parameters/GridLeaseId'
|
|
917
|
+
- $ref: '#/components/parameters/GridLeaseToken'
|
|
918
|
+
responses:
|
|
919
|
+
'200':
|
|
920
|
+
description: The heartbeat was recorded.
|
|
921
|
+
content:
|
|
922
|
+
application/json:
|
|
923
|
+
schema:
|
|
924
|
+
type: object
|
|
925
|
+
properties:
|
|
926
|
+
heartbeatedAt:
|
|
927
|
+
type: integer
|
|
928
|
+
description: Epoch milliseconds.
|
|
929
|
+
expiresAt:
|
|
930
|
+
type: integer
|
|
931
|
+
description: Epoch milliseconds.
|
|
932
|
+
example:
|
|
933
|
+
heartbeatedAt: 1791111630000
|
|
934
|
+
expiresAt: 1791113400000
|
|
935
|
+
'401':
|
|
936
|
+
$ref: '#/components/responses/Unauthorized'
|
|
937
|
+
'403':
|
|
938
|
+
$ref: '#/components/responses/GridLeaseForbidden'
|
|
939
|
+
'410':
|
|
940
|
+
description: The lease has ended (released, expired or swept).
|
|
941
|
+
content:
|
|
942
|
+
application/json:
|
|
943
|
+
schema:
|
|
944
|
+
$ref: '#/components/schemas/Error'
|
|
945
|
+
example:
|
|
946
|
+
error: gone
|
|
947
|
+
message: lease clxa1b2c30000s8k2f9d1e7aa is expired
|
|
948
|
+
'429':
|
|
949
|
+
$ref: '#/components/responses/RateLimited'
|
|
950
|
+
'500':
|
|
951
|
+
$ref: '#/components/responses/GridLeaseInternal'
|
|
952
|
+
/api/sdk/leases/{id}/extend:
|
|
953
|
+
post:
|
|
954
|
+
operationId: extendGridLease
|
|
955
|
+
summary: Extend a lease
|
|
956
|
+
description: |
|
|
957
|
+
Sets the lease to end `durationMs` from now (not from its current
|
|
958
|
+
end), and counts as a heartbeat. A lease never runs past 24 hours
|
|
959
|
+
after it was created: a later end is cut to that. A smaller
|
|
960
|
+
`durationMs` than the time left shortens the lease.
|
|
961
|
+
|
|
962
|
+
Needs the lease token in `x-xenon-lease-token`, role `MEMBER` or
|
|
963
|
+
above and the `devices` scope. An unknown id answers `403
|
|
964
|
+
token_mismatch`; an ended or expired lease answers `410`.
|
|
965
|
+
tags:
|
|
966
|
+
- Leases
|
|
967
|
+
parameters:
|
|
968
|
+
- $ref: '#/components/parameters/GridLeaseId'
|
|
969
|
+
- $ref: '#/components/parameters/GridLeaseToken'
|
|
970
|
+
requestBody:
|
|
971
|
+
required: true
|
|
972
|
+
content:
|
|
973
|
+
application/json:
|
|
974
|
+
schema:
|
|
975
|
+
type: object
|
|
976
|
+
required:
|
|
977
|
+
- durationMs
|
|
978
|
+
properties:
|
|
979
|
+
durationMs:
|
|
980
|
+
type: integer
|
|
981
|
+
description: How long from now the lease should last, in milliseconds.
|
|
982
|
+
example: 1800000
|
|
983
|
+
responses:
|
|
984
|
+
'200':
|
|
985
|
+
description: The lease's new end.
|
|
986
|
+
content:
|
|
987
|
+
application/json:
|
|
988
|
+
schema:
|
|
989
|
+
type: object
|
|
990
|
+
properties:
|
|
991
|
+
expiresAt:
|
|
992
|
+
type: integer
|
|
993
|
+
description: Epoch milliseconds.
|
|
994
|
+
example:
|
|
995
|
+
expiresAt: 1791115200000
|
|
996
|
+
'400':
|
|
997
|
+
description: '`durationMs` is missing or not a number.'
|
|
998
|
+
content:
|
|
999
|
+
application/json:
|
|
1000
|
+
schema:
|
|
1001
|
+
$ref: '#/components/schemas/Error'
|
|
1002
|
+
example:
|
|
1003
|
+
error: bad_request
|
|
1004
|
+
details: durationMs required
|
|
1005
|
+
'401':
|
|
1006
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1007
|
+
'403':
|
|
1008
|
+
$ref: '#/components/responses/GridLeaseForbidden'
|
|
1009
|
+
'410':
|
|
1010
|
+
description: The lease has ended (released, expired or swept).
|
|
1011
|
+
content:
|
|
1012
|
+
application/json:
|
|
1013
|
+
schema:
|
|
1014
|
+
$ref: '#/components/schemas/Error'
|
|
1015
|
+
example:
|
|
1016
|
+
error: gone
|
|
1017
|
+
'429':
|
|
1018
|
+
$ref: '#/components/responses/RateLimited'
|
|
1019
|
+
'500':
|
|
1020
|
+
$ref: '#/components/responses/GridLeaseInternal'
|
|
1021
|
+
/api/sdk/leases/{id}:
|
|
1022
|
+
delete:
|
|
1023
|
+
operationId: releaseGridLease
|
|
1024
|
+
summary: Release a lease
|
|
1025
|
+
description: |
|
|
1026
|
+
Ends the lease and gives back its ports. The device is freed unless
|
|
1027
|
+
an Appium session still holds it; that session keeps it until it
|
|
1028
|
+
ends. A lease past `expiresAt` that the sweep hasn't ended yet can
|
|
1029
|
+
still be released.
|
|
1030
|
+
|
|
1031
|
+
Needs the lease token in `x-xenon-lease-token`, role `MEMBER` or
|
|
1032
|
+
above and the `devices` scope. An unknown id answers `403
|
|
1033
|
+
token_mismatch`; a lease that has already ended answers `404`.
|
|
1034
|
+
tags:
|
|
1035
|
+
- Leases
|
|
1036
|
+
parameters:
|
|
1037
|
+
- $ref: '#/components/parameters/GridLeaseId'
|
|
1038
|
+
- $ref: '#/components/parameters/GridLeaseToken'
|
|
1039
|
+
responses:
|
|
1040
|
+
'204':
|
|
1041
|
+
description: The lease is released. No body.
|
|
1042
|
+
'401':
|
|
1043
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1044
|
+
'403':
|
|
1045
|
+
$ref: '#/components/responses/GridLeaseForbidden'
|
|
1046
|
+
'404':
|
|
1047
|
+
description: The lease has already ended.
|
|
1048
|
+
content:
|
|
1049
|
+
application/json:
|
|
1050
|
+
schema:
|
|
1051
|
+
$ref: '#/components/schemas/Error'
|
|
1052
|
+
example:
|
|
1053
|
+
error: not_found
|
|
1054
|
+
'429':
|
|
1055
|
+
$ref: '#/components/responses/RateLimited'
|
|
1056
|
+
'500':
|
|
1057
|
+
$ref: '#/components/responses/GridLeaseInternal'
|
|
1058
|
+
/api/sdk/version:
|
|
1059
|
+
get:
|
|
1060
|
+
operationId: getGridSdkVersion
|
|
1061
|
+
summary: Get the version and features for SDKs
|
|
1062
|
+
description: |
|
|
1063
|
+
The plugin's version and the SDK features this server supports, so
|
|
1064
|
+
a client can check compatibility before leasing. Needs role
|
|
1065
|
+
`MEMBER` or above.
|
|
1066
|
+
tags:
|
|
1067
|
+
- Leases
|
|
1068
|
+
responses:
|
|
1069
|
+
'200':
|
|
1070
|
+
description: The version and features.
|
|
1071
|
+
content:
|
|
1072
|
+
application/json:
|
|
1073
|
+
schema:
|
|
1074
|
+
type: object
|
|
1075
|
+
properties:
|
|
1076
|
+
pluginVersion:
|
|
1077
|
+
type: string
|
|
1078
|
+
supports:
|
|
1079
|
+
type: array
|
|
1080
|
+
items:
|
|
1081
|
+
type: string
|
|
1082
|
+
enum:
|
|
1083
|
+
- leases
|
|
1084
|
+
- ports
|
|
1085
|
+
- heartbeat
|
|
1086
|
+
example:
|
|
1087
|
+
pluginVersion: 2.12.0
|
|
1088
|
+
supports:
|
|
1089
|
+
- leases
|
|
1090
|
+
- ports
|
|
1091
|
+
- heartbeat
|
|
1092
|
+
'401':
|
|
1093
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1094
|
+
'403':
|
|
1095
|
+
$ref: '#/components/responses/Forbidden'
|
|
1096
|
+
'429':
|
|
1097
|
+
$ref: '#/components/responses/RateLimited'
|
|
1098
|
+
/api/ports/allocate:
|
|
1099
|
+
post:
|
|
1100
|
+
operationId: allocateGridPorts
|
|
1101
|
+
summary: Reserve driver ports on this server
|
|
1102
|
+
description: |
|
|
1103
|
+
Picks a free local port on this server for each purpose and records
|
|
1104
|
+
it as leased to `udid` for `durationMs` plus 5 minutes. A hub calls
|
|
1105
|
+
it on the server a device is attached to (itself included) when it
|
|
1106
|
+
creates a lease, with its node credentials. The device isn't
|
|
1107
|
+
checked to exist.
|
|
1108
|
+
|
|
1109
|
+
Needs role `ADMIN` and the `devices` scope.
|
|
1110
|
+
tags:
|
|
1111
|
+
- Leases
|
|
1112
|
+
requestBody:
|
|
1113
|
+
required: true
|
|
1114
|
+
content:
|
|
1115
|
+
application/json:
|
|
1116
|
+
schema:
|
|
1117
|
+
type: object
|
|
1118
|
+
required:
|
|
1119
|
+
- udid
|
|
1120
|
+
- host
|
|
1121
|
+
- purposes
|
|
1122
|
+
properties:
|
|
1123
|
+
udid:
|
|
1124
|
+
type: string
|
|
1125
|
+
example: emulator-5554
|
|
1126
|
+
host:
|
|
1127
|
+
type: string
|
|
1128
|
+
example: http://192.168.1.100:4723
|
|
1129
|
+
purposes:
|
|
1130
|
+
type: array
|
|
1131
|
+
minItems: 1
|
|
1132
|
+
items:
|
|
1133
|
+
$ref: '#/components/schemas/GridPortPurpose'
|
|
1134
|
+
durationMs:
|
|
1135
|
+
type: integer
|
|
1136
|
+
description: How long to hold the ports. Default 1800000 (30 minutes); a value of 0 or less uses the default.
|
|
1137
|
+
default: 1800000
|
|
1138
|
+
leaseId:
|
|
1139
|
+
type: string
|
|
1140
|
+
description: The lease the ports are for, if known.
|
|
1141
|
+
example:
|
|
1142
|
+
udid: emulator-5554
|
|
1143
|
+
host: http://192.168.1.100:4723
|
|
1144
|
+
purposes:
|
|
1145
|
+
- systemPort
|
|
1146
|
+
- chromedriverPort
|
|
1147
|
+
- mjpegServerPort
|
|
1148
|
+
durationMs: 1800000
|
|
1149
|
+
responses:
|
|
1150
|
+
'200':
|
|
1151
|
+
description: The port reserved for each purpose.
|
|
1152
|
+
content:
|
|
1153
|
+
application/json:
|
|
1154
|
+
schema:
|
|
1155
|
+
type: object
|
|
1156
|
+
properties:
|
|
1157
|
+
ports:
|
|
1158
|
+
type: object
|
|
1159
|
+
additionalProperties:
|
|
1160
|
+
type: integer
|
|
1161
|
+
example:
|
|
1162
|
+
ports:
|
|
1163
|
+
systemPort: 52311
|
|
1164
|
+
chromedriverPort: 52312
|
|
1165
|
+
mjpegServerPort: 52313
|
|
1166
|
+
'400':
|
|
1167
|
+
description: '`udid`, `host` or `purposes` is missing, or a purpose is unknown.'
|
|
1168
|
+
content:
|
|
1169
|
+
application/json:
|
|
1170
|
+
schema:
|
|
1171
|
+
$ref: '#/components/schemas/Error'
|
|
1172
|
+
example:
|
|
1173
|
+
error: bad_request
|
|
1174
|
+
details: udid, host, purposes (non-empty, valid) required
|
|
1175
|
+
'401':
|
|
1176
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1177
|
+
'403':
|
|
1178
|
+
$ref: '#/components/responses/Forbidden'
|
|
1179
|
+
'429':
|
|
1180
|
+
$ref: '#/components/responses/RateLimited'
|
|
1181
|
+
'500':
|
|
1182
|
+
description: A port could not be reserved; none of this request's ports are kept.
|
|
1183
|
+
content:
|
|
1184
|
+
application/json:
|
|
1185
|
+
schema:
|
|
1186
|
+
$ref: '#/components/schemas/Error'
|
|
1187
|
+
example:
|
|
1188
|
+
error: allocate_failed
|
|
1189
|
+
details: Unique constraint failed on the fields (`port`)
|
|
1190
|
+
/api/queue:
|
|
1191
|
+
get:
|
|
1192
|
+
operationId: listGridQueue
|
|
1193
|
+
summary: List the session requests waiting for a device
|
|
1194
|
+
description: |
|
|
1195
|
+
The session requests waiting for a free device, as the
|
|
1196
|
+
capabilities each client sent (`alwaysMatch` merged over the first
|
|
1197
|
+
`firstMatch`), with `capability_id` and `createdAt`.
|
|
1198
|
+
|
|
1199
|
+
An admin sees all of them. A member sees a request that names
|
|
1200
|
+
(`appium:udid`) a device they can see, or that they made, or that
|
|
1201
|
+
came from one of their teams; the rest are only counted, in
|
|
1202
|
+
`otherCount` of `GET /api/queue/summary`. A request queued by a
|
|
1203
|
+
server that didn't record who made it is only counted for a
|
|
1204
|
+
member.
|
|
1205
|
+
|
|
1206
|
+
Credentials are removed before a request is queued; any
|
|
1207
|
+
secret-named key a row still holds reads `***REDACTED***`, for
|
|
1208
|
+
every caller. Needs role `MEMBER` or above.
|
|
1209
|
+
tags:
|
|
1210
|
+
- Queue
|
|
1211
|
+
responses:
|
|
1212
|
+
'200':
|
|
1213
|
+
description: The waiting requests the caller may see, oldest first as stored.
|
|
1214
|
+
content:
|
|
1215
|
+
application/json:
|
|
1216
|
+
schema:
|
|
1217
|
+
type: array
|
|
1218
|
+
items:
|
|
1219
|
+
$ref: '#/components/schemas/GridQueuedRequest'
|
|
1220
|
+
example:
|
|
1221
|
+
- platformName: Android
|
|
1222
|
+
appium:automationName: UiAutomator2
|
|
1223
|
+
appium:udid: emulator-5554
|
|
1224
|
+
xe:options:
|
|
1225
|
+
buildId: build-2026-10-04-nightly
|
|
1226
|
+
capability_id: 3f2b8c1e-9a4d-4e7b-b2c6-1d8e5f7a9c0b
|
|
1227
|
+
createdAt: 1791111600000
|
|
1228
|
+
'401':
|
|
1229
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1230
|
+
'403':
|
|
1231
|
+
$ref: '#/components/responses/Forbidden'
|
|
1232
|
+
'429':
|
|
1233
|
+
$ref: '#/components/responses/RateLimited'
|
|
1234
|
+
/api/queue/length:
|
|
1235
|
+
get:
|
|
1236
|
+
operationId: getGridQueueLength
|
|
1237
|
+
summary: Count the session requests waiting
|
|
1238
|
+
description: |
|
|
1239
|
+
How many session requests are waiting in the whole queue, as a bare
|
|
1240
|
+
number. Every caller gets the same number, including requests they
|
|
1241
|
+
can't see in `GET /api/queue`; it equals `total` in the summary.
|
|
1242
|
+
Needs role `MEMBER` or above.
|
|
1243
|
+
tags:
|
|
1244
|
+
- Queue
|
|
1245
|
+
responses:
|
|
1246
|
+
'200':
|
|
1247
|
+
description: The number of waiting requests.
|
|
1248
|
+
content:
|
|
1249
|
+
application/json:
|
|
1250
|
+
schema:
|
|
1251
|
+
type: integer
|
|
1252
|
+
example: 3
|
|
1253
|
+
'401':
|
|
1254
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1255
|
+
'403':
|
|
1256
|
+
$ref: '#/components/responses/Forbidden'
|
|
1257
|
+
'429':
|
|
1258
|
+
$ref: '#/components/responses/RateLimited'
|
|
1259
|
+
/api/queue/summary:
|
|
1260
|
+
get:
|
|
1261
|
+
operationId: getGridQueueSummary
|
|
1262
|
+
summary: Summarise the session queue
|
|
1263
|
+
description: |
|
|
1264
|
+
Counts only. `total` and `byPlatform` cover the whole queue, as
|
|
1265
|
+
`GET /api/queue/length` does; `otherCount` is how many of them the
|
|
1266
|
+
caller doesn't get in detail from `GET /api/queue` (0 for an admin).
|
|
1267
|
+
`avgDurationMs` is the average of the platform's last 20 finished
|
|
1268
|
+
sessions, or 300000 (5 minutes) without history. Platforms are
|
|
1269
|
+
lowercase; a request naming none is `any`. Needs role `MEMBER` or
|
|
1270
|
+
above.
|
|
1271
|
+
tags:
|
|
1272
|
+
- Queue
|
|
1273
|
+
responses:
|
|
1274
|
+
'200':
|
|
1275
|
+
description: The queue's counts.
|
|
1276
|
+
content:
|
|
1277
|
+
application/json:
|
|
1278
|
+
schema:
|
|
1279
|
+
type: object
|
|
1280
|
+
properties:
|
|
1281
|
+
total:
|
|
1282
|
+
type: integer
|
|
1283
|
+
otherCount:
|
|
1284
|
+
type: integer
|
|
1285
|
+
byPlatform:
|
|
1286
|
+
type: object
|
|
1287
|
+
additionalProperties:
|
|
1288
|
+
type: object
|
|
1289
|
+
properties:
|
|
1290
|
+
count:
|
|
1291
|
+
type: integer
|
|
1292
|
+
avgDurationMs:
|
|
1293
|
+
type: integer
|
|
1294
|
+
example:
|
|
1295
|
+
total: 3
|
|
1296
|
+
otherCount: 1
|
|
1297
|
+
byPlatform:
|
|
1298
|
+
android:
|
|
1299
|
+
count: 2
|
|
1300
|
+
avgDurationMs: 412000
|
|
1301
|
+
ios:
|
|
1302
|
+
count: 1
|
|
1303
|
+
avgDurationMs: 300000
|
|
1304
|
+
'401':
|
|
1305
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1306
|
+
'403':
|
|
1307
|
+
$ref: '#/components/responses/Forbidden'
|
|
1308
|
+
'429':
|
|
1309
|
+
$ref: '#/components/responses/RateLimited'
|
|
1310
|
+
/api/queue/status/{capability_id}:
|
|
1311
|
+
get:
|
|
1312
|
+
operationId: getGridQueuedRequestStatus
|
|
1313
|
+
summary: Get a waiting request's position and wait
|
|
1314
|
+
description: |
|
|
1315
|
+
Where one waiting session request stands among the requests for
|
|
1316
|
+
its platform, and a rough wait: its position divided by the number
|
|
1317
|
+
of devices of that platform, times the average session duration
|
|
1318
|
+
(0 when it is first and a device is free).
|
|
1319
|
+
|
|
1320
|
+
`404` when the request has already been given a device, was
|
|
1321
|
+
dropped, or is one the caller can't see in `GET /api/queue`: the
|
|
1322
|
+
same answer as for an unknown id. Needs role `MEMBER` or above.
|
|
1323
|
+
tags:
|
|
1324
|
+
- Queue
|
|
1325
|
+
parameters:
|
|
1326
|
+
- in: path
|
|
1327
|
+
name: capability_id
|
|
1328
|
+
required: true
|
|
1329
|
+
description: The request's `capability_id`, as `GET /api/queue` lists it.
|
|
1330
|
+
schema:
|
|
1331
|
+
type: string
|
|
1332
|
+
example: 3f2b8c1e-9a4d-4e7b-b2c6-1d8e5f7a9c0b
|
|
1333
|
+
responses:
|
|
1334
|
+
'200':
|
|
1335
|
+
description: The request's place in the queue.
|
|
1336
|
+
content:
|
|
1337
|
+
application/json:
|
|
1338
|
+
schema:
|
|
1339
|
+
type: object
|
|
1340
|
+
properties:
|
|
1341
|
+
position:
|
|
1342
|
+
type: integer
|
|
1343
|
+
description: 1 is next.
|
|
1344
|
+
totalInQueue:
|
|
1345
|
+
type: integer
|
|
1346
|
+
description: Waiting requests for this platform (or any platform).
|
|
1347
|
+
etaInMs:
|
|
1348
|
+
type: integer
|
|
1349
|
+
platform:
|
|
1350
|
+
type: string
|
|
1351
|
+
matchedDevicesCount:
|
|
1352
|
+
type: integer
|
|
1353
|
+
availableDevicesCount:
|
|
1354
|
+
type: integer
|
|
1355
|
+
example:
|
|
1356
|
+
position: 2
|
|
1357
|
+
totalInQueue: 2
|
|
1358
|
+
etaInMs: 206000
|
|
1359
|
+
platform: android
|
|
1360
|
+
matchedDevicesCount: 4
|
|
1361
|
+
availableDevicesCount: 0
|
|
1362
|
+
'401':
|
|
1363
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1364
|
+
'403':
|
|
1365
|
+
$ref: '#/components/responses/Forbidden'
|
|
1366
|
+
'404':
|
|
1367
|
+
description: No such waiting request, or one the caller can't see.
|
|
1368
|
+
content:
|
|
1369
|
+
application/json:
|
|
1370
|
+
schema:
|
|
1371
|
+
$ref: '#/components/schemas/Error'
|
|
1372
|
+
example:
|
|
1373
|
+
error: Pending session not found
|
|
1374
|
+
'429':
|
|
1375
|
+
$ref: '#/components/responses/RateLimited'
|
|
1376
|
+
/api/register:
|
|
1377
|
+
post:
|
|
1378
|
+
operationId: reportGridNodeDevices
|
|
1379
|
+
summary: Report a node's devices to its hub
|
|
1380
|
+
description: |
|
|
1381
|
+
How a node keeps its hub's device list current. Nodes call it
|
|
1382
|
+
themselves, with the access key and token they were provisioned
|
|
1383
|
+
with (`XENON_HUB_ACCESS_KEY`, `XENON_HUB_TOKEN`); it isn't meant for
|
|
1384
|
+
other clients. Needs role `ADMIN` and the `devices` scope.
|
|
1385
|
+
|
|
1386
|
+
`type` selects the operation:
|
|
1387
|
+
|
|
1388
|
+
| `type` | Body | Effect |
|
|
1389
|
+
|---|---|---|
|
|
1390
|
+
| `add` | the node's devices | Adds or refreshes each one |
|
|
1391
|
+
| `remove` | devices, each with `udid` and `nodeId` or `host` | Forgets those devices |
|
|
1392
|
+
| `unregister` | `[]` | Forgets every device of the node named by `nodeId` or `host` in the query |
|
|
1393
|
+
|
|
1394
|
+
A report (`add`) updates only what the node observes: discovery
|
|
1395
|
+
fields such as name, version and ports, and health. Whether the
|
|
1396
|
+
device is busy on the node goes to `nodeBusy`, and a preview hold
|
|
1397
|
+
there to `nodeHold`. The hub's own settings (team, tags,
|
|
1398
|
+
reservation, block, its claim for a session) are never taken from
|
|
1399
|
+
a report, and a report of "free" never frees a device the hub has
|
|
1400
|
+
claimed.
|
|
1401
|
+
|
|
1402
|
+
A removal takes only the node's own rows: by `nodeId` when given,
|
|
1403
|
+
otherwise by exactly `host`. It never takes one of the hub's own
|
|
1404
|
+
devices, and an entry with no udid, or with neither `nodeId` nor
|
|
1405
|
+
`host`, takes nothing. Any other `type`, or none, changes nothing
|
|
1406
|
+
and still answers `200`.
|
|
1407
|
+
tags:
|
|
1408
|
+
- Hub-Node
|
|
1409
|
+
security:
|
|
1410
|
+
- AccessKeyAuth: []
|
|
1411
|
+
TokenAuth: []
|
|
1412
|
+
parameters:
|
|
1413
|
+
- in: query
|
|
1414
|
+
name: type
|
|
1415
|
+
required: true
|
|
1416
|
+
description: The operation.
|
|
1417
|
+
schema:
|
|
1418
|
+
type: string
|
|
1419
|
+
enum:
|
|
1420
|
+
- add
|
|
1421
|
+
- remove
|
|
1422
|
+
- unregister
|
|
1423
|
+
- in: query
|
|
1424
|
+
name: host
|
|
1425
|
+
required: false
|
|
1426
|
+
description: For `unregister`, the node's host, used when `nodeId` isn't sent.
|
|
1427
|
+
schema:
|
|
1428
|
+
type: string
|
|
1429
|
+
example: http://192.168.1.100:4723
|
|
1430
|
+
- in: query
|
|
1431
|
+
name: nodeId
|
|
1432
|
+
required: false
|
|
1433
|
+
description: For `unregister`, the node's id; every device the node reported goes, whatever its host.
|
|
1434
|
+
schema:
|
|
1435
|
+
type: string
|
|
1436
|
+
example: 6b1e4c2a-8f3d-4a9e-b7c5-2d0f1e8a9b3c
|
|
1437
|
+
requestBody:
|
|
1438
|
+
required: true
|
|
1439
|
+
content:
|
|
1440
|
+
application/json:
|
|
1441
|
+
schema:
|
|
1442
|
+
type: array
|
|
1443
|
+
items:
|
|
1444
|
+
$ref: '#/components/schemas/GridNodeDeviceReport'
|
|
1445
|
+
examples:
|
|
1446
|
+
add:
|
|
1447
|
+
summary: type=add
|
|
1448
|
+
value:
|
|
1449
|
+
- udid: emulator-5554
|
|
1450
|
+
host: http://192.168.1.100:4723
|
|
1451
|
+
nodeId: 6b1e4c2a-8f3d-4a9e-b7c5-2d0f1e8a9b3c
|
|
1452
|
+
name: Pixel 7 API 34
|
|
1453
|
+
platform: android
|
|
1454
|
+
sdk: '14'
|
|
1455
|
+
state: device
|
|
1456
|
+
deviceType: emulator
|
|
1457
|
+
realDevice: false
|
|
1458
|
+
busy: false
|
|
1459
|
+
offline: false
|
|
1460
|
+
healthStatus: Healthy
|
|
1461
|
+
remove:
|
|
1462
|
+
summary: type=remove
|
|
1463
|
+
value:
|
|
1464
|
+
- udid: emulator-5554
|
|
1465
|
+
host: http://192.168.1.100:4723
|
|
1466
|
+
nodeId: 6b1e4c2a-8f3d-4a9e-b7c5-2d0f1e8a9b3c
|
|
1467
|
+
unregister:
|
|
1468
|
+
summary: type=unregister
|
|
1469
|
+
value: []
|
|
1470
|
+
responses:
|
|
1471
|
+
'200':
|
|
1472
|
+
description: The report was applied (or ignored, for an unknown `type`).
|
|
1473
|
+
content:
|
|
1474
|
+
application/json:
|
|
1475
|
+
schema:
|
|
1476
|
+
$ref: '#/components/schemas/Success'
|
|
1477
|
+
example:
|
|
1478
|
+
success: true
|
|
1479
|
+
'401':
|
|
1480
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1481
|
+
'403':
|
|
1482
|
+
$ref: '#/components/responses/Forbidden'
|
|
1483
|
+
'429':
|
|
1484
|
+
$ref: '#/components/responses/RateLimited'
|
|
1485
|
+
/api/node:
|
|
1486
|
+
get:
|
|
1487
|
+
operationId: listGridNodeHosts
|
|
1488
|
+
summary: List the hosts that have devices
|
|
1489
|
+
description: |
|
|
1490
|
+
The distinct `host` of every device the caller can see: this
|
|
1491
|
+
server and, on a hub, each node. A host whose devices all belong to
|
|
1492
|
+
teams the caller isn't in isn't listed. Needs role `MEMBER` or
|
|
1493
|
+
above.
|
|
1494
|
+
tags:
|
|
1495
|
+
- Hub-Node
|
|
1496
|
+
responses:
|
|
1497
|
+
'200':
|
|
1498
|
+
description: The hosts.
|
|
1499
|
+
content:
|
|
1500
|
+
application/json:
|
|
1501
|
+
schema:
|
|
1502
|
+
type: array
|
|
1503
|
+
items:
|
|
1504
|
+
type: string
|
|
1505
|
+
example:
|
|
1506
|
+
- http://192.168.1.10:4723
|
|
1507
|
+
- http://192.168.1.100:4723
|
|
1508
|
+
'401':
|
|
1509
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1510
|
+
'403':
|
|
1511
|
+
$ref: '#/components/responses/Forbidden'
|
|
1512
|
+
'429':
|
|
1513
|
+
$ref: '#/components/responses/RateLimited'
|
|
1514
|
+
/api/node/status:
|
|
1515
|
+
get:
|
|
1516
|
+
operationId: getGridLocalNodeStatus
|
|
1517
|
+
summary: List the devices attached to this server
|
|
1518
|
+
description: |
|
|
1519
|
+
Runs device discovery on this server now (Android and iOS, real
|
|
1520
|
+
devices and emulators or simulators) and lists what it finds, with
|
|
1521
|
+
each device's `state` as discovery reports it. Devices of other
|
|
1522
|
+
servers aren't included. Needs role `ADMIN` and the `admin` scope.
|
|
1523
|
+
tags:
|
|
1524
|
+
- Hub-Node
|
|
1525
|
+
responses:
|
|
1526
|
+
'200':
|
|
1527
|
+
$ref: '#/components/responses/GridNodeStatusOk'
|
|
1528
|
+
'401':
|
|
1529
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1530
|
+
'403':
|
|
1531
|
+
$ref: '#/components/responses/Forbidden'
|
|
1532
|
+
'429':
|
|
1533
|
+
$ref: '#/components/responses/RateLimited'
|
|
1534
|
+
/api/node/{host}/status:
|
|
1535
|
+
get:
|
|
1536
|
+
operationId: getGridNodeStatus
|
|
1537
|
+
summary: List the devices attached to one server
|
|
1538
|
+
description: |
|
|
1539
|
+
When `host` is this server's bind address (`bindHostOrIp`), the
|
|
1540
|
+
same as `GET /api/node/status`. Otherwise the hub looks for a
|
|
1541
|
+
device whose host contains `host` and asks that server's
|
|
1542
|
+
`/xenon/api/node/status`, returning its answer. That request
|
|
1543
|
+
carries no credentials, so it works only against a node with auth
|
|
1544
|
+
disabled; a node that refuses it, or can't be reached, answers
|
|
1545
|
+
`502`.
|
|
1546
|
+
|
|
1547
|
+
Needs role `ADMIN` and the `admin` scope.
|
|
1548
|
+
tags:
|
|
1549
|
+
- Hub-Node
|
|
1550
|
+
parameters:
|
|
1551
|
+
- in: path
|
|
1552
|
+
name: host
|
|
1553
|
+
required: true
|
|
1554
|
+
description: This server's bind address, or part of a node's host (such as its IP).
|
|
1555
|
+
schema:
|
|
1556
|
+
type: string
|
|
1557
|
+
example: 192.168.1.100
|
|
1558
|
+
responses:
|
|
1559
|
+
'200':
|
|
1560
|
+
$ref: '#/components/responses/GridNodeStatusOk'
|
|
1561
|
+
'401':
|
|
1562
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1563
|
+
'403':
|
|
1564
|
+
$ref: '#/components/responses/Forbidden'
|
|
1565
|
+
'404':
|
|
1566
|
+
description: No device is listed under that host, so there is nowhere to ask. Plain text.
|
|
1567
|
+
content:
|
|
1568
|
+
text/html:
|
|
1569
|
+
schema:
|
|
1570
|
+
type: string
|
|
1571
|
+
example: Host 192.168.1.200 does not have any devices listed in database. I don't know how to forward request to that host
|
|
1572
|
+
'429':
|
|
1573
|
+
$ref: '#/components/responses/RateLimited'
|
|
1574
|
+
'502':
|
|
1575
|
+
description: The node refused the request or couldn't be reached.
|
|
1576
|
+
content:
|
|
1577
|
+
application/json:
|
|
1578
|
+
schema:
|
|
1579
|
+
$ref: '#/components/schemas/Error'
|
|
1580
|
+
example:
|
|
1581
|
+
error: node_unreachable
|
|
1582
|
+
message: "Could not get http://192.168.1.200:4723's status: connect ECONNREFUSED 192.168.1.200:4723"
|
|
1583
|
+
/api/node/sessions/{sessionId}:
|
|
1584
|
+
get:
|
|
1585
|
+
operationId: getGridNodeSessionStatus
|
|
1586
|
+
summary: Say whether a session exists on this node
|
|
1587
|
+
description: |
|
|
1588
|
+
Node only: a hub asks it about each session it routes to the node,
|
|
1589
|
+
about every 30 s, instead of sending a WebDriver command, which
|
|
1590
|
+
would keep an abandoned session alive. It runs no command. Only a
|
|
1591
|
+
server started with `hub` set has this route.
|
|
1592
|
+
|
|
1593
|
+
It is answered before the login, with what a command to that
|
|
1594
|
+
session would need. With per-command auth off
|
|
1595
|
+
(`XENON_REQUIRE_COMMAND_AUTH`) or auth disabled, no credential.
|
|
1596
|
+
With it on, the hub's session token for that session in
|
|
1597
|
+
`x-xenon-hub-token`; any other caller gets WebDriver's
|
|
1598
|
+
unknown-session answer, `404`, and a hub key set that can't be
|
|
1599
|
+
fetched is `503`. It isn't rate-limited.
|
|
1600
|
+
|
|
1601
|
+
Every answer from a node carries `x-xenon-node-sessions: 1`, so a
|
|
1602
|
+
hub can tell a node that has the route from an older one.
|
|
1603
|
+
tags:
|
|
1604
|
+
- Hub-Node
|
|
1605
|
+
security:
|
|
1606
|
+
- HubToken: []
|
|
1607
|
+
- {}
|
|
1608
|
+
parameters:
|
|
1609
|
+
- $ref: '#/components/parameters/GridNodeSessionId'
|
|
1610
|
+
responses:
|
|
1611
|
+
'200':
|
|
1612
|
+
description: Whether this node's Appium has the session.
|
|
1613
|
+
headers:
|
|
1614
|
+
x-xenon-node-sessions:
|
|
1615
|
+
description: Always `1` on a node.
|
|
1616
|
+
schema:
|
|
1617
|
+
type: string
|
|
1618
|
+
example: '1'
|
|
1619
|
+
content:
|
|
1620
|
+
application/json:
|
|
1621
|
+
schema:
|
|
1622
|
+
type: object
|
|
1623
|
+
properties:
|
|
1624
|
+
value:
|
|
1625
|
+
type: object
|
|
1626
|
+
properties:
|
|
1627
|
+
sessionId:
|
|
1628
|
+
type: string
|
|
1629
|
+
exists:
|
|
1630
|
+
type: boolean
|
|
1631
|
+
example:
|
|
1632
|
+
value:
|
|
1633
|
+
sessionId: 9d4c2f1a-7b3e-4e8a-a6d5-0c1b2e3f4a5b
|
|
1634
|
+
exists: true
|
|
1635
|
+
'401':
|
|
1636
|
+
$ref: '#/components/responses/GridNotANodeUnauthorized'
|
|
1637
|
+
'404':
|
|
1638
|
+
$ref: '#/components/responses/GridNodeUnknownSession'
|
|
1639
|
+
'503':
|
|
1640
|
+
$ref: '#/components/responses/GridNodeTokenUnavailable'
|
|
1641
|
+
/api/node/sessions/{sessionId}/metrics:
|
|
1642
|
+
get:
|
|
1643
|
+
operationId: getGridNodeSessionMetrics
|
|
1644
|
+
summary: Collect a session's CPU and memory from this node
|
|
1645
|
+
description: |
|
|
1646
|
+
Node only: a hub collects the CPU and memory samples a node has
|
|
1647
|
+
taken for a session on one of its phones, every 10 s and once more
|
|
1648
|
+
when the session ends. The node keeps them in memory: the newest
|
|
1649
|
+
900 per session, an ended session's for 10 minutes. Only a server
|
|
1650
|
+
started with `hub` set has this route.
|
|
1651
|
+
|
|
1652
|
+
`after` asks for the samples newer than that time, and the node
|
|
1653
|
+
drops the older ones, so a later call never returns them again.
|
|
1654
|
+
`state` is `sampling`, `stopped` (the sampler gave up), `ended`, or
|
|
1655
|
+
`off` (the node has nothing for this session).
|
|
1656
|
+
|
|
1657
|
+
It is answered before the login, with the same rule as
|
|
1658
|
+
`GET /api/node/sessions/{sessionId}`: no credential with
|
|
1659
|
+
per-command auth off or auth disabled, otherwise the hub's session
|
|
1660
|
+
token for that session. Every answer from a node carries
|
|
1661
|
+
`x-xenon-node-metrics: 1`. It isn't rate-limited.
|
|
1662
|
+
tags:
|
|
1663
|
+
- Hub-Node
|
|
1664
|
+
security:
|
|
1665
|
+
- HubToken: []
|
|
1666
|
+
- {}
|
|
1667
|
+
parameters:
|
|
1668
|
+
- $ref: '#/components/parameters/GridNodeSessionId'
|
|
1669
|
+
- in: query
|
|
1670
|
+
name: after
|
|
1671
|
+
required: false
|
|
1672
|
+
description: Epoch milliseconds; only newer samples are returned, and older ones are dropped. Anything that isn't a plain number is ignored.
|
|
1673
|
+
schema:
|
|
1674
|
+
type: number
|
|
1675
|
+
example: 1791111600000
|
|
1676
|
+
responses:
|
|
1677
|
+
'200':
|
|
1678
|
+
description: The session's samples.
|
|
1679
|
+
headers:
|
|
1680
|
+
x-xenon-node-metrics:
|
|
1681
|
+
description: Always `1` on a node.
|
|
1682
|
+
schema:
|
|
1683
|
+
type: string
|
|
1684
|
+
example: '1'
|
|
1685
|
+
content:
|
|
1686
|
+
application/json:
|
|
1687
|
+
schema:
|
|
1688
|
+
type: object
|
|
1689
|
+
properties:
|
|
1690
|
+
value:
|
|
1691
|
+
$ref: '#/components/schemas/GridNodeMetrics'
|
|
1692
|
+
example:
|
|
1693
|
+
value:
|
|
1694
|
+
platform: android
|
|
1695
|
+
state: sampling
|
|
1696
|
+
samples:
|
|
1697
|
+
- at: 1791111602000
|
|
1698
|
+
deviceCpuPct: 23.5
|
|
1699
|
+
deviceMemMb: 4120
|
|
1700
|
+
deviceMemTotalMb: 7680
|
|
1701
|
+
appCpuPct: 11.2
|
|
1702
|
+
appMemMb: 286
|
|
1703
|
+
appId: com.example.shop
|
|
1704
|
+
- at: 1791111604000
|
|
1705
|
+
deviceCpuPct: 19.1
|
|
1706
|
+
deviceMemMb: 4128
|
|
1707
|
+
deviceMemTotalMb: 7680
|
|
1708
|
+
appCpuPct: 8.7
|
|
1709
|
+
appMemMb: 289
|
|
1710
|
+
appId: com.example.shop
|
|
1711
|
+
'401':
|
|
1712
|
+
$ref: '#/components/responses/GridNotANodeUnauthorized'
|
|
1713
|
+
'404':
|
|
1714
|
+
$ref: '#/components/responses/GridNodeUnknownSession'
|
|
1715
|
+
'503':
|
|
1716
|
+
$ref: '#/components/responses/GridNodeTokenUnavailable'
|
|
1717
|
+
/api/status:
|
|
1718
|
+
get:
|
|
1719
|
+
operationId: getGridServerStatus
|
|
1720
|
+
summary: Check that the server answers
|
|
1721
|
+
description: |
|
|
1722
|
+
A signed-in liveness check. `version` comes from the npm
|
|
1723
|
+
environment, so a server not started through an npm script reports
|
|
1724
|
+
`unknown (not running from npm package)`; `GET /api/ping` reports
|
|
1725
|
+
the plugin's version. Needs role `MEMBER` or above.
|
|
1726
|
+
tags:
|
|
1727
|
+
- Health & Ops
|
|
1728
|
+
responses:
|
|
1729
|
+
'200':
|
|
1730
|
+
description: The server is up.
|
|
1731
|
+
content:
|
|
1732
|
+
application/json:
|
|
1733
|
+
schema:
|
|
1734
|
+
type: object
|
|
1735
|
+
properties:
|
|
1736
|
+
status:
|
|
1737
|
+
type: string
|
|
1738
|
+
enum:
|
|
1739
|
+
- ok
|
|
1740
|
+
version:
|
|
1741
|
+
type: string
|
|
1742
|
+
example:
|
|
1743
|
+
status: ok
|
|
1744
|
+
version: unknown (not running from npm package)
|
|
1745
|
+
'401':
|
|
1746
|
+
$ref: '#/components/responses/Unauthorized'
|
|
1747
|
+
'403':
|
|
1748
|
+
$ref: '#/components/responses/Forbidden'
|
|
1749
|
+
'429':
|
|
1750
|
+
$ref: '#/components/responses/RateLimited'
|
|
1751
|
+
/api/webdriver:
|
|
1752
|
+
get:
|
|
1753
|
+
operationId: getGridWebDriverBasePath
|
|
1754
|
+
summary: Get this server's WebDriver base path
|
|
1755
|
+
description: |
|
|
1756
|
+
Where this server's Appium WebDriver API lives, relative to its
|
|
1757
|
+
origin: `''` for Appium's default, or a path such as `/wd/hub`. A
|
|
1758
|
+
hub asks each node before it sends the node a session or a command
|
|
1759
|
+
(and caches the answer for a minute), since hub and node needn't
|
|
1760
|
+
share a base path. Public: no credential and no rate limit.
|
|
1761
|
+
tags:
|
|
1762
|
+
- Health & Ops
|
|
1763
|
+
security: []
|
|
1764
|
+
responses:
|
|
1765
|
+
'200':
|
|
1766
|
+
description: The base path.
|
|
1767
|
+
content:
|
|
1768
|
+
application/json:
|
|
1769
|
+
schema:
|
|
1770
|
+
type: object
|
|
1771
|
+
properties:
|
|
1772
|
+
basePath:
|
|
1773
|
+
type: string
|
|
1774
|
+
description: Empty, or a path starting with `/` and without a trailing `/`.
|
|
1775
|
+
example:
|
|
1776
|
+
basePath: /wd/hub
|
|
1777
|
+
components:
|
|
1778
|
+
parameters:
|
|
1779
|
+
GridSessionIdQuery:
|
|
1780
|
+
in: query
|
|
1781
|
+
name: sessionId
|
|
1782
|
+
required: false
|
|
1783
|
+
description: Return only the device running this Appium session (an object), or an empty body when none does.
|
|
1784
|
+
schema:
|
|
1785
|
+
type: string
|
|
1786
|
+
example: 9d4c2f1a-7b3e-4e8a-a6d5-0c1b2e3f4a5b
|
|
1787
|
+
GridReservationUdid:
|
|
1788
|
+
in: path
|
|
1789
|
+
name: udid
|
|
1790
|
+
required: true
|
|
1791
|
+
description: The device's udid.
|
|
1792
|
+
schema:
|
|
1793
|
+
type: string
|
|
1794
|
+
example: emulator-5554
|
|
1795
|
+
GridReservationHost:
|
|
1796
|
+
in: path
|
|
1797
|
+
name: host
|
|
1798
|
+
required: true
|
|
1799
|
+
description: The device's host, URL-encoded.
|
|
1800
|
+
schema:
|
|
1801
|
+
type: string
|
|
1802
|
+
example: http%3A%2F%2F192.168.1.100%3A4723
|
|
1803
|
+
GridLeaseId:
|
|
1804
|
+
in: path
|
|
1805
|
+
name: id
|
|
1806
|
+
required: true
|
|
1807
|
+
description: The lease's `leaseId`.
|
|
1808
|
+
schema:
|
|
1809
|
+
type: string
|
|
1810
|
+
example: clxa1b2c30000s8k2f9d1e7aa
|
|
1811
|
+
GridLeaseToken:
|
|
1812
|
+
in: header
|
|
1813
|
+
name: x-xenon-lease-token
|
|
1814
|
+
required: true
|
|
1815
|
+
description: The `leaseToken` from the lease's creation.
|
|
1816
|
+
schema:
|
|
1817
|
+
type: string
|
|
1818
|
+
example: 7f3c9a1e5b2d48f0a6c4e8b1d3f5a7c9e2b4d6f8a0c1e3b5d7f9a2c4e6b8d0f1
|
|
1819
|
+
GridNodeSessionId:
|
|
1820
|
+
in: path
|
|
1821
|
+
name: sessionId
|
|
1822
|
+
required: true
|
|
1823
|
+
description: The Appium session's id on the node.
|
|
1824
|
+
schema:
|
|
1825
|
+
type: string
|
|
1826
|
+
example: 9d4c2f1a-7b3e-4e8a-a6d5-0c1b2e3f4a5b
|
|
1827
|
+
headers:
|
|
1828
|
+
GridDeprecation:
|
|
1829
|
+
description: Always `true`; reservations are deprecated in favour of leases.
|
|
1830
|
+
schema:
|
|
1831
|
+
type: string
|
|
1832
|
+
example: 'true'
|
|
1833
|
+
GridSunset:
|
|
1834
|
+
description: When reservations will be removed.
|
|
1835
|
+
schema:
|
|
1836
|
+
type: string
|
|
1837
|
+
example: '2027-01-01T00:00:00Z'
|
|
1838
|
+
GridLink:
|
|
1839
|
+
description: Where the lease API that replaces reservations is described (`rel="alternate"`).
|
|
1840
|
+
schema:
|
|
1841
|
+
type: string
|
|
1842
|
+
responses:
|
|
1843
|
+
GridDeviceListOk:
|
|
1844
|
+
description: |
|
|
1845
|
+
The visible devices; with `sessionId`, the one device running that
|
|
1846
|
+
session, or an empty body when none does.
|
|
1847
|
+
content:
|
|
1848
|
+
application/json:
|
|
1849
|
+
schema:
|
|
1850
|
+
oneOf:
|
|
1851
|
+
- type: array
|
|
1852
|
+
items:
|
|
1853
|
+
$ref: '#/components/schemas/GridDevice'
|
|
1854
|
+
- $ref: '#/components/schemas/GridDevice'
|
|
1855
|
+
example:
|
|
1856
|
+
- udid: 00008110-00084CE80E51401E
|
|
1857
|
+
name: iPhone 14 Pro
|
|
1858
|
+
platform: ios
|
|
1859
|
+
host: http://192.168.1.10:4723
|
|
1860
|
+
busy: true
|
|
1861
|
+
session_id: 9d4c2f1a-7b3e-4e8a-a6d5-0c1b2e3f4a5b
|
|
1862
|
+
state: device
|
|
1863
|
+
sdk: '17.4'
|
|
1864
|
+
deviceType: real
|
|
1865
|
+
realDevice: true
|
|
1866
|
+
offline: false
|
|
1867
|
+
userBlocked: false
|
|
1868
|
+
teamId: clx9k2v0a0000qz8r4h7m3n2p
|
|
1869
|
+
teamName: Checkout
|
|
1870
|
+
healthStatus: Healthy
|
|
1871
|
+
batteryLevel: 87
|
|
1872
|
+
- udid: emulator-5554
|
|
1873
|
+
name: Pixel 7 API 34
|
|
1874
|
+
platform: android
|
|
1875
|
+
host: http://192.168.1.100:4723
|
|
1876
|
+
nodeId: 6b1e4c2a-8f3d-4a9e-b7c5-2d0f1e8a9b3c
|
|
1877
|
+
busy: false
|
|
1878
|
+
session_id: null
|
|
1879
|
+
state: device
|
|
1880
|
+
sdk: '14'
|
|
1881
|
+
deviceType: emulator
|
|
1882
|
+
realDevice: false
|
|
1883
|
+
offline: false
|
|
1884
|
+
userBlocked: false
|
|
1885
|
+
teamId: null
|
|
1886
|
+
teamName: null
|
|
1887
|
+
tags:
|
|
1888
|
+
- lab-row-3
|
|
1889
|
+
reservedBy: null
|
|
1890
|
+
reservedUntil: null
|
|
1891
|
+
healthStatus: Healthy
|
|
1892
|
+
batteryLevel: 100
|
|
1893
|
+
screenWidth: '1080'
|
|
1894
|
+
screenHeight: '2400'
|
|
1895
|
+
GridDeviceNotFound:
|
|
1896
|
+
description: No such device, or one outside your teams (the two answer the same).
|
|
1897
|
+
content:
|
|
1898
|
+
application/json:
|
|
1899
|
+
schema:
|
|
1900
|
+
$ref: '#/components/schemas/Error'
|
|
1901
|
+
example:
|
|
1902
|
+
success: false
|
|
1903
|
+
error: Device not found
|
|
1904
|
+
GridDeviceSelectorMissing:
|
|
1905
|
+
description: '`udid` or `host` is missing, or not a string.'
|
|
1906
|
+
content:
|
|
1907
|
+
application/json:
|
|
1908
|
+
schema:
|
|
1909
|
+
$ref: '#/components/schemas/Error'
|
|
1910
|
+
example:
|
|
1911
|
+
success: false
|
|
1912
|
+
error: bad_request
|
|
1913
|
+
message: udid and host are required
|
|
1914
|
+
GridNotReserved:
|
|
1915
|
+
description: The device isn't reserved.
|
|
1916
|
+
content:
|
|
1917
|
+
application/json:
|
|
1918
|
+
schema:
|
|
1919
|
+
$ref: '#/components/schemas/Error'
|
|
1920
|
+
example:
|
|
1921
|
+
success: false
|
|
1922
|
+
error: Device is not reserved
|
|
1923
|
+
GridReservationFailed:
|
|
1924
|
+
description: The reservation store failed.
|
|
1925
|
+
content:
|
|
1926
|
+
application/json:
|
|
1927
|
+
schema:
|
|
1928
|
+
$ref: '#/components/schemas/Error'
|
|
1929
|
+
example:
|
|
1930
|
+
success: false
|
|
1931
|
+
error: Failed to reserve device
|
|
1932
|
+
GridLeaseForbidden:
|
|
1933
|
+
description: |
|
|
1934
|
+
No lease token (`missing_lease_token`), a token that doesn't match or
|
|
1935
|
+
an unknown lease id (`token_mismatch`), the role or scope is missing,
|
|
1936
|
+
or the same-origin check failed.
|
|
1937
|
+
content:
|
|
1938
|
+
application/json:
|
|
1939
|
+
schema:
|
|
1940
|
+
$ref: '#/components/schemas/Error'
|
|
1941
|
+
examples:
|
|
1942
|
+
missing:
|
|
1943
|
+
value:
|
|
1944
|
+
error: missing_lease_token
|
|
1945
|
+
mismatch:
|
|
1946
|
+
value:
|
|
1947
|
+
error: token_mismatch
|
|
1948
|
+
scope:
|
|
1949
|
+
value:
|
|
1950
|
+
error: insufficient scope
|
|
1951
|
+
GridLeaseInternal:
|
|
1952
|
+
description: An unexpected error.
|
|
1953
|
+
content:
|
|
1954
|
+
application/json:
|
|
1955
|
+
schema:
|
|
1956
|
+
$ref: '#/components/schemas/Error'
|
|
1957
|
+
example:
|
|
1958
|
+
error: internal
|
|
1959
|
+
message: Can't reach database server
|
|
1960
|
+
GridNodeStatusOk:
|
|
1961
|
+
description: The attached devices.
|
|
1962
|
+
content:
|
|
1963
|
+
application/json:
|
|
1964
|
+
schema:
|
|
1965
|
+
type: array
|
|
1966
|
+
items:
|
|
1967
|
+
type: object
|
|
1968
|
+
properties:
|
|
1969
|
+
udid:
|
|
1970
|
+
type: string
|
|
1971
|
+
host:
|
|
1972
|
+
type: string
|
|
1973
|
+
state:
|
|
1974
|
+
type: string
|
|
1975
|
+
platform:
|
|
1976
|
+
type: string
|
|
1977
|
+
enum:
|
|
1978
|
+
- ios
|
|
1979
|
+
- android
|
|
1980
|
+
example:
|
|
1981
|
+
- udid: emulator-5554
|
|
1982
|
+
host: http://192.168.1.100:4723
|
|
1983
|
+
state: device
|
|
1984
|
+
platform: android
|
|
1985
|
+
- udid: 00008110-00084CE80E51401E
|
|
1986
|
+
host: http://192.168.1.100:4723
|
|
1987
|
+
state: device
|
|
1988
|
+
platform: ios
|
|
1989
|
+
GridNotANodeUnauthorized:
|
|
1990
|
+
description: |
|
|
1991
|
+
Never sent by a node. A server that isn't a node has no such route,
|
|
1992
|
+
so its login answers first: `401` without credentials (and `404`
|
|
1993
|
+
with them).
|
|
1994
|
+
content:
|
|
1995
|
+
application/json:
|
|
1996
|
+
schema:
|
|
1997
|
+
$ref: '#/components/schemas/Error'
|
|
1998
|
+
example:
|
|
1999
|
+
error: unauthenticated
|
|
2000
|
+
GridNodeUnknownSession:
|
|
2001
|
+
description: |
|
|
2002
|
+
Per-command auth is on and the request has no valid hub session
|
|
2003
|
+
token for this session. WebDriver's unknown-session answer, the same
|
|
2004
|
+
whether or not the session exists.
|
|
2005
|
+
content:
|
|
2006
|
+
application/json:
|
|
2007
|
+
schema:
|
|
2008
|
+
$ref: '#/components/schemas/GridWebDriverError'
|
|
2009
|
+
example:
|
|
2010
|
+
value:
|
|
2011
|
+
error: invalid session id
|
|
2012
|
+
message: A session is either terminated or not started
|
|
2013
|
+
stacktrace: ''
|
|
2014
|
+
GridNodeTokenUnavailable:
|
|
2015
|
+
description: The hub's token could not be checked (its key set couldn't be fetched). Try again.
|
|
2016
|
+
content:
|
|
2017
|
+
application/json:
|
|
2018
|
+
schema:
|
|
2019
|
+
$ref: '#/components/schemas/GridWebDriverError'
|
|
2020
|
+
example:
|
|
2021
|
+
value:
|
|
2022
|
+
error: unknown error
|
|
2023
|
+
message: Xenon could not verify access to this session. Try again.
|
|
2024
|
+
stacktrace: ''
|
|
2025
|
+
schemas:
|
|
2026
|
+
GridDevice:
|
|
2027
|
+
description: A device as the device lists return it, with its team's name.
|
|
2028
|
+
allOf:
|
|
2029
|
+
- $ref: '#/components/schemas/Device'
|
|
2030
|
+
- type: object
|
|
2031
|
+
properties:
|
|
2032
|
+
teamName:
|
|
2033
|
+
type: string
|
|
2034
|
+
nullable: true
|
|
2035
|
+
description: The team's name; null for the shared pool.
|
|
2036
|
+
tags:
|
|
2037
|
+
type: array
|
|
2038
|
+
items:
|
|
2039
|
+
type: string
|
|
2040
|
+
nodeBusy:
|
|
2041
|
+
type: boolean
|
|
2042
|
+
nullable: true
|
|
2043
|
+
description: On a hub, whether the node last reported the device busy.
|
|
2044
|
+
nodeHold:
|
|
2045
|
+
type: string
|
|
2046
|
+
nullable: true
|
|
2047
|
+
description: On a hub, the preview hold the node last reported (`manual_<userId>_<udid>`).
|
|
2048
|
+
dashboard_link:
|
|
2049
|
+
type: string
|
|
2050
|
+
description: Only with the Appium dashboard plugin installed.
|
|
2051
|
+
total_session_count:
|
|
2052
|
+
type: integer
|
|
2053
|
+
description: Only with the Appium dashboard plugin installed.
|
|
2054
|
+
GridDeviceSelector:
|
|
2055
|
+
type: object
|
|
2056
|
+
description: Picks a device by both its `udid` and its `host`, exactly as `GET /api/devices` shows them.
|
|
2057
|
+
required:
|
|
2058
|
+
- udid
|
|
2059
|
+
- host
|
|
2060
|
+
properties:
|
|
2061
|
+
udid:
|
|
2062
|
+
type: string
|
|
2063
|
+
host:
|
|
2064
|
+
type: string
|
|
2065
|
+
GridReservationDuration:
|
|
2066
|
+
description: '`1h`, `2h`, `4h` or `8h`, or a number of milliseconds. A reservation lasts from 1 minute to 24 hours, and an extension may not end it more than 24 hours from now.'
|
|
2067
|
+
oneOf:
|
|
2068
|
+
- type: string
|
|
2069
|
+
enum:
|
|
2070
|
+
- 1h
|
|
2071
|
+
- 2h
|
|
2072
|
+
- 4h
|
|
2073
|
+
- 8h
|
|
2074
|
+
- type: integer
|
|
2075
|
+
example: 5400000
|
|
2076
|
+
GridReservation:
|
|
2077
|
+
type: object
|
|
2078
|
+
properties:
|
|
2079
|
+
udid:
|
|
2080
|
+
type: string
|
|
2081
|
+
host:
|
|
2082
|
+
type: string
|
|
2083
|
+
name:
|
|
2084
|
+
type: string
|
|
2085
|
+
platform:
|
|
2086
|
+
type: string
|
|
2087
|
+
enum:
|
|
2088
|
+
- ios
|
|
2089
|
+
- android
|
|
2090
|
+
reservedBy:
|
|
2091
|
+
type: string
|
|
2092
|
+
reservedUntil:
|
|
2093
|
+
type: integer
|
|
2094
|
+
description: Epoch milliseconds.
|
|
2095
|
+
reservationReason:
|
|
2096
|
+
type: string
|
|
2097
|
+
nullable: true
|
|
2098
|
+
remainingMs:
|
|
2099
|
+
type: integer
|
|
2100
|
+
GridLeaseCreateRequest:
|
|
2101
|
+
type: object
|
|
2102
|
+
required:
|
|
2103
|
+
- filters
|
|
2104
|
+
properties:
|
|
2105
|
+
filters:
|
|
2106
|
+
type: object
|
|
2107
|
+
required:
|
|
2108
|
+
- platform
|
|
2109
|
+
properties:
|
|
2110
|
+
platform:
|
|
2111
|
+
type: string
|
|
2112
|
+
enum:
|
|
2113
|
+
- android
|
|
2114
|
+
- ios
|
|
2115
|
+
udid:
|
|
2116
|
+
type: string
|
|
2117
|
+
description: Lease this device only.
|
|
2118
|
+
deviceType:
|
|
2119
|
+
type: string
|
|
2120
|
+
enum:
|
|
2121
|
+
- real
|
|
2122
|
+
- simulator
|
|
2123
|
+
- emulator
|
|
2124
|
+
tags:
|
|
2125
|
+
type: array
|
|
2126
|
+
description: The device must carry every one of these tags.
|
|
2127
|
+
items:
|
|
2128
|
+
type: string
|
|
2129
|
+
platformVersion:
|
|
2130
|
+
type: string
|
|
2131
|
+
description: The OS version must equal this (compared as a version).
|
|
2132
|
+
minSDK:
|
|
2133
|
+
type: string
|
|
2134
|
+
example: '13'
|
|
2135
|
+
maxSDK:
|
|
2136
|
+
type: string
|
|
2137
|
+
example: '15'
|
|
2138
|
+
sdk:
|
|
2139
|
+
type: string
|
|
2140
|
+
description: The OS version, as `platformVersion`; `platformVersion` wins if both are sent.
|
|
2141
|
+
deviceName:
|
|
2142
|
+
type: string
|
|
2143
|
+
description: The device's name, ignoring case.
|
|
2144
|
+
durationMs:
|
|
2145
|
+
type: integer
|
|
2146
|
+
description: How long the lease lasts. Kept between 60000 (1 minute) and 86400000 (24 hours).
|
|
2147
|
+
default: 1800000
|
|
2148
|
+
heartbeatSeconds:
|
|
2149
|
+
type: integer
|
|
2150
|
+
description: How often you will send a heartbeat. Kept between 10 and 300.
|
|
2151
|
+
default: 30
|
|
2152
|
+
reason:
|
|
2153
|
+
type: string
|
|
2154
|
+
buildId:
|
|
2155
|
+
type: string
|
|
2156
|
+
description: Carried into `appiumCapabilities` as `xe:options.buildId`.
|
|
2157
|
+
GridLease:
|
|
2158
|
+
type: object
|
|
2159
|
+
properties:
|
|
2160
|
+
leaseId:
|
|
2161
|
+
type: string
|
|
2162
|
+
leaseToken:
|
|
2163
|
+
type: string
|
|
2164
|
+
description: The lease's secret. Shown only here.
|
|
2165
|
+
device:
|
|
2166
|
+
type: object
|
|
2167
|
+
properties:
|
|
2168
|
+
udid:
|
|
2169
|
+
type: string
|
|
2170
|
+
host:
|
|
2171
|
+
type: string
|
|
2172
|
+
platform:
|
|
2173
|
+
type: string
|
|
2174
|
+
sdk:
|
|
2175
|
+
type: string
|
|
2176
|
+
name:
|
|
2177
|
+
type: string
|
|
2178
|
+
screen:
|
|
2179
|
+
type: object
|
|
2180
|
+
properties:
|
|
2181
|
+
width:
|
|
2182
|
+
type: string
|
|
2183
|
+
nullable: true
|
|
2184
|
+
height:
|
|
2185
|
+
type: string
|
|
2186
|
+
nullable: true
|
|
2187
|
+
realDevice:
|
|
2188
|
+
type: boolean
|
|
2189
|
+
expiresAt:
|
|
2190
|
+
type: integer
|
|
2191
|
+
description: Epoch milliseconds.
|
|
2192
|
+
heartbeatSeconds:
|
|
2193
|
+
type: integer
|
|
2194
|
+
allocatedPorts:
|
|
2195
|
+
type: object
|
|
2196
|
+
description: '`systemPort`, `chromedriverPort` and `mjpegServerPort` for Android; `wdaLocalPort` and `mjpegServerPort` for iOS.'
|
|
2197
|
+
additionalProperties:
|
|
2198
|
+
type: integer
|
|
2199
|
+
appiumCapabilities:
|
|
2200
|
+
type: object
|
|
2201
|
+
description: Capabilities for `POST /session`, including `xe:options.leaseId` and `xe:options.leaseToken`.
|
|
2202
|
+
additionalProperties: true
|
|
2203
|
+
GridPortPurpose:
|
|
2204
|
+
type: string
|
|
2205
|
+
enum:
|
|
2206
|
+
- systemPort
|
|
2207
|
+
- wdaLocalPort
|
|
2208
|
+
- chromedriverPort
|
|
2209
|
+
- mjpegServerPort
|
|
2210
|
+
GridQueuedRequest:
|
|
2211
|
+
type: object
|
|
2212
|
+
description: The capabilities a waiting session request sent, merged, plus its queue fields.
|
|
2213
|
+
properties:
|
|
2214
|
+
capability_id:
|
|
2215
|
+
type: string
|
|
2216
|
+
createdAt:
|
|
2217
|
+
type: integer
|
|
2218
|
+
description: Epoch milliseconds.
|
|
2219
|
+
additionalProperties: true
|
|
2220
|
+
GridNodeDeviceReport:
|
|
2221
|
+
type: object
|
|
2222
|
+
description: |
|
|
2223
|
+
One device in a node's report. For `add`, the node's full view of the
|
|
2224
|
+
device (the fields of `Device`); for `remove`, `udid` with `nodeId`
|
|
2225
|
+
or `host`.
|
|
2226
|
+
required:
|
|
2227
|
+
- udid
|
|
2228
|
+
properties:
|
|
2229
|
+
udid:
|
|
2230
|
+
type: string
|
|
2231
|
+
host:
|
|
2232
|
+
type: string
|
|
2233
|
+
nodeId:
|
|
2234
|
+
type: string
|
|
2235
|
+
busy:
|
|
2236
|
+
type: boolean
|
|
2237
|
+
description: Busy on the node; stored as `nodeBusy`.
|
|
2238
|
+
session_id:
|
|
2239
|
+
type: string
|
|
2240
|
+
nullable: true
|
|
2241
|
+
description: The node's session or hold; a preview hold is stored as `nodeHold`.
|
|
2242
|
+
additionalProperties: true
|
|
2243
|
+
GridNodeMetrics:
|
|
2244
|
+
type: object
|
|
2245
|
+
properties:
|
|
2246
|
+
platform:
|
|
2247
|
+
type: string
|
|
2248
|
+
description: Lowercase; empty when `state` is `off`.
|
|
2249
|
+
state:
|
|
2250
|
+
type: string
|
|
2251
|
+
enum:
|
|
2252
|
+
- sampling
|
|
2253
|
+
- stopped
|
|
2254
|
+
- ended
|
|
2255
|
+
- 'off'
|
|
2256
|
+
samples:
|
|
2257
|
+
type: array
|
|
2258
|
+
items:
|
|
2259
|
+
type: object
|
|
2260
|
+
properties:
|
|
2261
|
+
at:
|
|
2262
|
+
type: integer
|
|
2263
|
+
description: Epoch milliseconds.
|
|
2264
|
+
deviceCpuPct:
|
|
2265
|
+
type: number
|
|
2266
|
+
nullable: true
|
|
2267
|
+
deviceMemMb:
|
|
2268
|
+
type: number
|
|
2269
|
+
nullable: true
|
|
2270
|
+
deviceMemTotalMb:
|
|
2271
|
+
type: number
|
|
2272
|
+
nullable: true
|
|
2273
|
+
appCpuPct:
|
|
2274
|
+
type: number
|
|
2275
|
+
nullable: true
|
|
2276
|
+
appMemMb:
|
|
2277
|
+
type: number
|
|
2278
|
+
nullable: true
|
|
2279
|
+
appId:
|
|
2280
|
+
type: string
|
|
2281
|
+
nullable: true
|
|
2282
|
+
description: The app the app figures are for (Android), else null.
|
|
2283
|
+
GridWebDriverError:
|
|
2284
|
+
type: object
|
|
2285
|
+
description: A WebDriver-style error, as Appium answers one.
|
|
2286
|
+
properties:
|
|
2287
|
+
value:
|
|
2288
|
+
type: object
|
|
2289
|
+
properties:
|
|
2290
|
+
error:
|
|
2291
|
+
type: string
|
|
2292
|
+
message:
|
|
2293
|
+
type: string
|
|
2294
|
+
stacktrace:
|
|
2295
|
+
type: string
|