@browserstack/mcp-server 2.2.2 → 2.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3233 @@
1
+ {
2
+ "schema_version": 1,
3
+ "version": "0.1",
4
+ "build_id": "appautomate-demo",
5
+ "appautomate": {
6
+ "summary": "BrowserStack App Automate: run a real Android or iOS device and drive the app on it step by step. The account side lists the devices this account can use, including private ones, uploads and lists app builds, reports parallel-session capacity, and keeps each session's record \u2014 status, shareable link and video \u2014 after the device is released. The session side starts a device with an installed build and drives it: read the UI tree, find and tap elements, type, swipe, screenshot, press keys, rotate, switch between the native app and a web view, push and pull files, move the simulated GPS, change the network profile, launch, close and uninstall apps, answer biometric prompts and feed images to the camera. Apps are RUN here \u2014 unlike Test Management, which authors cases, and Test Reporting, which records results pushed in from a runner. Web browsers are not here, and neither is App Live: it has no API an agent can drive, so agent sessions run on App Automate and consume its minutes. A build must exist before a session starts and can only be uploaded from a public link, never a file on disk \u2014 so media for camera injection must come from elsewhere. The default Appium server is 1.x and rejects every `mobile:` command; ask for 2.19.0.",
7
+ "routing_terms": [
8
+ "app automate",
9
+ "appium",
10
+ "appium session",
11
+ "webdriver",
12
+ "real device",
13
+ "mobile app",
14
+ "drive the app",
15
+ "tap",
16
+ "swipe",
17
+ "scroll",
18
+ "ui tree",
19
+ "page source",
20
+ "device list",
21
+ "upload app",
22
+ "app build",
23
+ "parallel sessions",
24
+ "plan",
25
+ "screenshot",
26
+ "session video",
27
+ "public link",
28
+ "private device",
29
+ "network profile",
30
+ "launch app",
31
+ "close app",
32
+ "restart app",
33
+ "uninstall app",
34
+ "biometric",
35
+ "fingerprint",
36
+ "face id",
37
+ "camera injection",
38
+ "qr code",
39
+ "annotate",
40
+ "keyboard",
41
+ "keyboard open",
42
+ "home button",
43
+ "volume",
44
+ "hardware button",
45
+ "gps",
46
+ "location",
47
+ "foreground app",
48
+ "adb",
49
+ "device property",
50
+ "getprop",
51
+ "os build",
52
+ "upload apk",
53
+ "upload ipa",
54
+ "local build"
55
+ ],
56
+ "base_url": "https://hub-cloud.browserstack.com",
57
+ "auth": {
58
+ "type": "http",
59
+ "scheme": "basic"
60
+ },
61
+ "capabilities": [
62
+ {
63
+ "name": "create_session",
64
+ "method": "POST",
65
+ "path": "/wd/hub/session",
66
+ "mode": "write",
67
+ "entity": "app_session",
68
+ "intent": "Start a live Appium session on a real Android or iOS device with an uploaded app \u2014 use this before reading the screen, tapping, typing or swiping in the app.",
69
+ "guidance": [
70
+ "The app must already be uploaded: pass the bs:// URL from upload_app_build or list_app_builds, or the custom_id it was uploaded under, which always resolves to the newest build with that id.",
71
+ "Allocation and app install usually take 20\u201330 seconds; the response carries the new session id at value.sessionId \u2014 carry it into every other app_session call.",
72
+ "The session ends itself after idle_timeout seconds with no command (90 by default, 300 at most) and after 2 hours in total. Keep calling, or call get_window_rect as a cheap keep-alive, while deciding the next step.",
73
+ "Every session occupies a device and consumes App Automate minutes until it ends: always finish with execute_script setSessionStatus and then delete_session.",
74
+ "To let a person watch the device live while the agent drives it, set interactive_debugging true and leave video on; they then open the running session on the App Automate dashboard and start the interactive session there. That window is two-way: a person's taps and scrolls reach the same device, so ask them to watch only, or re-read the page source before every action.",
75
+ "device_name and platform_version must be a combination App Automate offers; an unknown pair fails with a session-not-created error naming the capability.",
76
+ "Set appium_version to 2.19.0 for any session that will use execute_script with a `mobile:` command; the default server is 1.x and answers those with a 404 listing the commands it does know.",
77
+ "enable_biometric, enable_camera_image_injection and upload_media only take effect when set here \u2014 none of them can be turned on once the session is running.",
78
+ "Credentials belong in the Authorization header, never inside bstack:options: capabilities are echoed back in session-not-created errors.",
79
+ "device_name and platform_version come from list_devices; get_plan first if sessions are queueing rather than starting.",
80
+ "Treat a response without value.sessionId as a failed start even when the status is 200, and treat a failed call the same way: allocation can outlast the client's own timeout, so BrowserStack may have started a session the answer never named. One is then holding a device and consuming minutes until its idle timeout.",
81
+ "After a start that failed or returned no session id, look for a session left running on the account and end it rather than starting another.",
82
+ "The response carries no link of any kind \u2014 it is the session id and the negotiated capabilities only. Call get_session for the links: browser_url to watch the run on the dashboard, and public_url to share it with someone who has no BrowserStack account."
83
+ ],
84
+ "body": [
85
+ {
86
+ "name": "platform_name",
87
+ "type": "string",
88
+ "required": true,
89
+ "values": [
90
+ "android",
91
+ "ios"
92
+ ],
93
+ "json_path": "/capabilities/alwaysMatch/platformName",
94
+ "example": "android"
95
+ },
96
+ {
97
+ "name": "device_name",
98
+ "type": "string",
99
+ "required": true,
100
+ "json_path": "/capabilities/alwaysMatch/appium:deviceName",
101
+ "description": "Device model exactly as App Automate lists it.",
102
+ "example": "Google Pixel 6"
103
+ },
104
+ {
105
+ "name": "platform_version",
106
+ "type": "string",
107
+ "json_path": "/capabilities/alwaysMatch/appium:platformVersion",
108
+ "description": "OS version of that device, as App Automate lists it. Omit to let App Automate pick one for the device.",
109
+ "example": "12.0"
110
+ },
111
+ {
112
+ "name": "app",
113
+ "type": "string",
114
+ "required": true,
115
+ "json_path": "/capabilities/alwaysMatch/appium:app",
116
+ "description": "The bs:// app URL returned by an App Automate upload, or the custom_id given at upload time (the latest app with that id is used).",
117
+ "example": "bs://0123456789abcdef0123456789abcdef01234567"
118
+ },
119
+ {
120
+ "name": "auto_grant_permissions",
121
+ "type": "boolean",
122
+ "json_path": "/capabilities/alwaysMatch/appium:autoGrantPermissions",
123
+ "description": "Android only: grant every runtime permission at install so permission dialogs do not appear.",
124
+ "example": true
125
+ },
126
+ {
127
+ "name": "idle_timeout",
128
+ "type": "integer",
129
+ "minimum": 0,
130
+ "maximum": 900,
131
+ "default": 90,
132
+ "json_path": "/capabilities/alwaysMatch/bstack:options/idleTimeout",
133
+ "description": "Seconds the session may sit without a command before App Automate ends it. Raise it when an agent drives the session step by step and may pause to think, or while a human watches; the device is held, and billed, for the whole idle period.",
134
+ "example": 300
135
+ },
136
+ {
137
+ "name": "appium_version",
138
+ "type": "string",
139
+ "json_path": "/capabilities/alwaysMatch/bstack:options/appiumVersion",
140
+ "description": "Appium server version to run. The default is 1.x, which has none of the `mobile:` commands execute_script relies on \u2014 they come back 404 with a list of what that server does support. Set 2.19.0 unless something needs the older server.",
141
+ "example": "2.19.0"
142
+ },
143
+ {
144
+ "name": "project_name",
145
+ "type": "string",
146
+ "json_path": "/capabilities/alwaysMatch/bstack:options/projectName",
147
+ "description": "Groups sessions on the App Automate dashboard.",
148
+ "example": "Checkout flows"
149
+ },
150
+ {
151
+ "name": "build_name",
152
+ "type": "string",
153
+ "json_path": "/capabilities/alwaysMatch/bstack:options/buildName",
154
+ "description": "Build the session is filed under on the dashboard.",
155
+ "example": "agent-exploration"
156
+ },
157
+ {
158
+ "name": "session_name",
159
+ "type": "string",
160
+ "json_path": "/capabilities/alwaysMatch/bstack:options/sessionName",
161
+ "description": "Name shown for this session on the dashboard.",
162
+ "example": "Add a product to the cart"
163
+ },
164
+ {
165
+ "name": "interactive_debugging",
166
+ "type": "boolean",
167
+ "json_path": "/capabilities/alwaysMatch/bstack:options/interactiveDebugging",
168
+ "description": "Let a person watch and interact with the device from the dashboard while the session runs. Off by default on Android; needs video left on.",
169
+ "example": true
170
+ },
171
+ {
172
+ "name": "video",
173
+ "type": "boolean",
174
+ "json_path": "/capabilities/alwaysMatch/bstack:options/video",
175
+ "description": "Record a video of the session, viewable on the dashboard afterwards. On by default; set false to skip recording when the screen shows sensitive data. interactive_debugging stops working if this is false.",
176
+ "default": true,
177
+ "example": false
178
+ },
179
+ {
180
+ "name": "automation_name",
181
+ "type": "string",
182
+ "values": [
183
+ "UiAutomator2",
184
+ "XCUITest"
185
+ ],
186
+ "json_path": "/capabilities/alwaysMatch/appium:automationName",
187
+ "description": "Driver to use. Omit to let the hub pick by platform; name it when a session must not fall back to another driver.",
188
+ "example": "UiAutomator2"
189
+ },
190
+ {
191
+ "name": "auto_accept_alerts",
192
+ "type": "boolean",
193
+ "json_path": "/capabilities/alwaysMatch/appium:autoAcceptAlerts",
194
+ "description": "iOS only: dismiss system dialogs automatically. Leave false to see and answer permission prompts yourself.",
195
+ "example": false
196
+ },
197
+ {
198
+ "name": "new_command_timeout",
199
+ "type": "integer",
200
+ "minimum": 0,
201
+ "json_path": "/capabilities/alwaysMatch/appium:newCommandTimeout",
202
+ "description": "Seconds the Appium server itself waits for the next command before ending the session. Distinct from idle_timeout, which is App Automate's own backstop; the shorter of the two wins.",
203
+ "example": 300
204
+ },
205
+ {
206
+ "name": "language",
207
+ "type": "string",
208
+ "json_path": "/capabilities/alwaysMatch/appium:language",
209
+ "description": "Device language code.",
210
+ "example": "fr"
211
+ },
212
+ {
213
+ "name": "locale",
214
+ "type": "string",
215
+ "json_path": "/capabilities/alwaysMatch/appium:locale",
216
+ "description": "Device locale.",
217
+ "example": "fr_CA"
218
+ },
219
+ {
220
+ "name": "timezone",
221
+ "type": "string",
222
+ "json_path": "/capabilities/alwaysMatch/bstack:options/timezone",
223
+ "description": "Device time zone, as a city name rather than an offset.",
224
+ "example": "Paris"
225
+ },
226
+ {
227
+ "name": "gps_location",
228
+ "type": "string",
229
+ "pattern": "^-?\\d+(\\.\\d+)?,-?\\d+(\\.\\d+)?$",
230
+ "json_path": "/capabilities/alwaysMatch/bstack:options/gpsLocation",
231
+ "description": "Starting GPS position as 'latitude,longitude'. Use set_geo_location to move it later.",
232
+ "example": "48.8566,2.3522"
233
+ },
234
+ {
235
+ "name": "network_profile",
236
+ "type": "string",
237
+ "json_path": "/capabilities/alwaysMatch/bstack:options/networkProfile",
238
+ "values": [
239
+ "no-network",
240
+ "2g-gprs-good",
241
+ "2g-gprs-lossy",
242
+ "3g-umts-good",
243
+ "3g-umts-lossy",
244
+ "4g-lte-good",
245
+ "4g-lte-advanced-good",
246
+ "4g-lte-advanced-lossy"
247
+ ],
248
+ "description": "Start the session under a named network profile. set_session_network changes it later.",
249
+ "example": "3g-umts-good"
250
+ },
251
+ {
252
+ "name": "network_logs",
253
+ "type": "boolean",
254
+ "json_path": "/capabilities/alwaysMatch/bstack:options/networkLogs",
255
+ "description": "Capture the app's network traffic, readable from the session afterwards.",
256
+ "example": true
257
+ },
258
+ {
259
+ "name": "enable_biometric",
260
+ "type": "boolean",
261
+ "json_path": "/capabilities/alwaysMatch/bstack:options/enableBiometric",
262
+ "description": "Instrument the app so a fingerprint, Touch ID or Face ID prompt can be answered during the session. Must be set at start; it cannot be turned on later.",
263
+ "example": true
264
+ },
265
+ {
266
+ "name": "enable_camera_image_injection",
267
+ "type": "boolean",
268
+ "json_path": "/capabilities/alwaysMatch/bstack:options/enableCameraImageInjection",
269
+ "description": "Instrument the app so an uploaded image can be fed to its camera. Must be set at start; it cannot be turned on later.",
270
+ "example": true
271
+ },
272
+ {
273
+ "name": "upload_media",
274
+ "type": "array",
275
+ "json_path": "/capabilities/alwaysMatch/bstack:options/uploadMedia",
276
+ "maxItems": 10,
277
+ "description": "media:// URLs of files to preload into the device's gallery or storage before the app opens.",
278
+ "example": [
279
+ "media://0123456789abcdef0123456789abcdef01234567"
280
+ ]
281
+ },
282
+ {
283
+ "name": "device_tunnel",
284
+ "type": "boolean",
285
+ "json_path": "/capabilities/alwaysMatch/bstack:options/deviceTunnel",
286
+ "description": "Open a tunnel to the device. Private (dedicated) devices only; BrowserStack rejects it on shared ones.",
287
+ "example": true
288
+ }
289
+ ],
290
+ "returns": [
291
+ "value.sessionId",
292
+ "value.capabilities"
293
+ ],
294
+ "responses": {
295
+ "200": {
296
+ "description": "Session created.",
297
+ "schema": {
298
+ "$schema": "NewSessionResponse"
299
+ }
300
+ },
301
+ "401": {
302
+ "$response": "Unauthorized"
303
+ },
304
+ "500": {
305
+ "$response": "WebDriverError"
306
+ }
307
+ }
308
+ },
309
+ {
310
+ "name": "get_page_source",
311
+ "method": "GET",
312
+ "path": "/wd/hub/session/{session_id}/source",
313
+ "mode": "read",
314
+ "entity": "app_session",
315
+ "intent": "Read the full UI tree of the screen currently shown in the app \u2014 use this to see what is on screen and choose what to tap, type into or scroll before acting.",
316
+ "guidance": [
317
+ "Returns the whole hierarchy as one XML string in value, unfiltered. On Android each node carries text, content-desc, resource-id, clickable, scrollable and bounds; on iOS name, label, value and x/y/width/height.",
318
+ "Read it again after every action: the tree is a snapshot, and element ids from find_element go stale once the screen changes.",
319
+ "Android bounds are \"[x1,y1][x2,y2]\" in device pixels \u2014 the centre of that box is the coordinate to use with perform_actions.",
320
+ "A product-specific attribute is the best locator: resource-id for find_element using \"id\", content-desc for \"accessibility id\".",
321
+ "Inside a web view this returns the page's HTML instead of the native tree, and a real page runs to hundreds of kilobytes where a native screen is a few. Switch back to NATIVE_APP before reading the tree again, or search the HTML for what you need rather than reading all of it."
322
+ ],
323
+ "path_params": [
324
+ {
325
+ "name": "session_id",
326
+ "type": "string",
327
+ "required": true,
328
+ "description": "value.sessionId from create_session.",
329
+ "example": "0123456789abcdef0123456789abcdef01234567"
330
+ }
331
+ ],
332
+ "returns": [
333
+ "value"
334
+ ],
335
+ "responses": {
336
+ "200": {
337
+ "description": "The UI tree as an XML string.",
338
+ "schema": {
339
+ "$schema": "StringValueResponse"
340
+ }
341
+ },
342
+ "404": {
343
+ "$response": "WebDriverError"
344
+ }
345
+ },
346
+ "disabled": true
347
+ },
348
+ {
349
+ "name": "find_element",
350
+ "method": "POST",
351
+ "path": "/wd/hub/session/{session_id}/element",
352
+ "mode": "read",
353
+ "entity": "app_session",
354
+ "intent": "Locate one element on the current screen by id, accessibility id, XPath or UiAutomator selector \u2014 use this to get the element id that click_element and send_keys act on.",
355
+ "guidance": [
356
+ "The element id comes back under the W3C key value[\"element-6066-11e4-a52e-4f735466cecf\"]; pass that string as element_id.",
357
+ "Take the selector from get_page_source rather than guessing: resource-id \u2192 \"id\", content-desc (Android) or name (iOS) \u2192 \"accessibility id\".",
358
+ "No match is a 404 with error \"no such element\" \u2014 re-read the page source; the screen may have changed or the element may be off-screen and need a scroll first.",
359
+ "\"-android uiautomator\" accepts a UiSelector expression, which can scroll an element into view: new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text(\"Checkout\"))."
360
+ ],
361
+ "path_params": [
362
+ {
363
+ "name": "session_id",
364
+ "type": "string",
365
+ "required": true,
366
+ "example": "0123456789abcdef0123456789abcdef01234567"
367
+ }
368
+ ],
369
+ "body": [
370
+ {
371
+ "name": "using",
372
+ "type": "string",
373
+ "required": true,
374
+ "values": [
375
+ "id",
376
+ "accessibility id",
377
+ "xpath",
378
+ "class name",
379
+ "-android uiautomator",
380
+ "-ios predicate string",
381
+ "-ios class chain"
382
+ ],
383
+ "example": "accessibility id"
384
+ },
385
+ {
386
+ "name": "value",
387
+ "type": "string",
388
+ "required": true,
389
+ "description": "The selector itself, interpreted according to using.",
390
+ "example": "nav-cart"
391
+ }
392
+ ],
393
+ "returns": [
394
+ "value"
395
+ ],
396
+ "responses": {
397
+ "200": {
398
+ "description": "The matching element reference.",
399
+ "schema": {
400
+ "$schema": "ElementResponse"
401
+ }
402
+ },
403
+ "404": {
404
+ "$response": "WebDriverError"
405
+ }
406
+ }
407
+ },
408
+ {
409
+ "name": "click_element",
410
+ "method": "POST",
411
+ "path": "/wd/hub/session/{session_id}/element/{element_id}/click",
412
+ "mode": "write",
413
+ "entity": "app_session",
414
+ "intent": "Tap an element in the app \u2014 use this to press a button, open a menu item, select a list row or focus a field found with find_element.",
415
+ "guidance": [
416
+ "element_id must come from find_element on the current screen; after the screen changes it is stale and fails with \"stale element reference\".",
417
+ "To tap something that has no usable locator, tap its bounds centre with perform_actions instead.",
418
+ "Read get_page_source afterwards to confirm the tap had the intended effect \u2014 a 200 only means the tap was delivered.",
419
+ "A keyboard covers the lower part of the screen and a tap aimed underneath it lands on the keyboard while still reporting success. When the target sits low, call is_keyboard_shown and then hide_keyboard before tapping."
420
+ ],
421
+ "path_params": [
422
+ {
423
+ "name": "session_id",
424
+ "type": "string",
425
+ "required": true,
426
+ "example": "0123456789abcdef0123456789abcdef01234567"
427
+ },
428
+ {
429
+ "name": "element_id",
430
+ "type": "string",
431
+ "required": true,
432
+ "description": "value[\"element-6066-11e4-a52e-4f735466cecf\"] from find_element.",
433
+ "example": "00000000-0000-0001-ffff-ffff00000012"
434
+ }
435
+ ],
436
+ "returns": [
437
+ "value"
438
+ ],
439
+ "responses": {
440
+ "200": {
441
+ "description": "Tap delivered; value is null.",
442
+ "schema": {
443
+ "$schema": "NullValueResponse"
444
+ }
445
+ },
446
+ "404": {
447
+ "$response": "WebDriverError"
448
+ }
449
+ }
450
+ },
451
+ {
452
+ "name": "send_keys",
453
+ "method": "POST",
454
+ "path": "/wd/hub/session/{session_id}/element/{element_id}/value",
455
+ "mode": "write",
456
+ "entity": "app_session",
457
+ "intent": "Type text into an input field in the app \u2014 use this to fill a search box, login form or any editable field found with find_element.",
458
+ "guidance": [
459
+ "REPLACES whatever the field already holds rather than adding to it \u2014 measured on Android with and without refocusing between calls. Read the field back from get_page_source if the resulting value matters, and do not build up a value with successive calls.",
460
+ "The element must be an editable field (EditText on Android, TextField on iOS); typing into anything else fails with \"invalid element state\".",
461
+ "The on-screen keyboard may stay open afterwards and cover lower elements \u2014 re-read the page source before the next tap.",
462
+ "The W3C specification says this appends, and other Appium drivers may; do not rely on either behaviour across platforms without checking. There is no separate way to empty a field on this surface: the standard clear endpoint answers 200 without reliably emptying the field, so typing the value you want is the way to set it.",
463
+ "The keyboard is usually left open afterwards: call hide_keyboard before tapping anything below the field, and is_keyboard_shown to confirm it closed.",
464
+ "Sending a newline submits the field and closes the keyboard on iOS, which is the most reliable way to dismiss it there."
465
+ ],
466
+ "path_params": [
467
+ {
468
+ "name": "session_id",
469
+ "type": "string",
470
+ "required": true,
471
+ "example": "0123456789abcdef0123456789abcdef01234567"
472
+ },
473
+ {
474
+ "name": "element_id",
475
+ "type": "string",
476
+ "required": true,
477
+ "example": "00000000-0000-0001-ffff-ffff00000012"
478
+ }
479
+ ],
480
+ "body": [
481
+ {
482
+ "name": "text",
483
+ "type": "string",
484
+ "required": true,
485
+ "description": "The characters to type.",
486
+ "example": "Galaxy S20"
487
+ }
488
+ ],
489
+ "returns": [
490
+ "value"
491
+ ],
492
+ "responses": {
493
+ "200": {
494
+ "description": "Text typed; value is null.",
495
+ "schema": {
496
+ "$schema": "NullValueResponse"
497
+ }
498
+ },
499
+ "404": {
500
+ "$response": "WebDriverError"
501
+ }
502
+ }
503
+ },
504
+ {
505
+ "name": "perform_actions",
506
+ "method": "POST",
507
+ "path": "/wd/hub/session/{session_id}/actions",
508
+ "mode": "write",
509
+ "entity": "app_session",
510
+ "intent": "Perform touch gestures on the device screen by coordinates \u2014 tap a point, swipe or scroll a list, long-press, or drag \u2014 using W3C pointer actions.",
511
+ "guidance": [
512
+ "Coordinates are device pixels in the same space as the page-source bounds; take them from there rather than from a screenshot, whose height can include the system bars.",
513
+ "Tap: one touch pointer with pointerMove(duration 0, x, y), pointerDown(button 0), pause(100), pointerUp(button 0).",
514
+ "Swipe up to scroll content down: pointerMove to a low point, pointerDown, pointerMove(duration 600) to a higher point, pointerUp. Keep the start and end inside the scrollable element's bounds.",
515
+ "Long-press: replace the pause duration with 1000 or more.",
516
+ "For a scroll without computing coordinates, execute_script with \"mobile: scrollGesture\" is simpler and reports whether more content remains.",
517
+ "To go back on an iOS screen that ignores press_back, swipe in from the left edge: a pointer sequence starting at exactly x=0 and moving right in several short steps. A single long move does not register."
518
+ ],
519
+ "path_params": [
520
+ {
521
+ "name": "session_id",
522
+ "type": "string",
523
+ "required": true,
524
+ "example": "0123456789abcdef0123456789abcdef01234567"
525
+ }
526
+ ],
527
+ "body": [
528
+ {
529
+ "name": "actions",
530
+ "type": "array",
531
+ "required": true,
532
+ "description": "W3C input sources. For touch use one source of type \"pointer\" with parameters {\"pointerType\":\"touch\"} and an actions list of pointerMove / pointerDown / pause / pointerUp items.",
533
+ "fields": [
534
+ {
535
+ "name": "type",
536
+ "type": "string",
537
+ "required": true,
538
+ "values": [
539
+ "pointer",
540
+ "key",
541
+ "none"
542
+ ]
543
+ },
544
+ {
545
+ "name": "id",
546
+ "type": "string",
547
+ "required": true
548
+ },
549
+ {
550
+ "name": "parameters",
551
+ "type": "object"
552
+ },
553
+ {
554
+ "name": "actions",
555
+ "type": "array",
556
+ "required": true
557
+ }
558
+ ],
559
+ "example": [
560
+ {
561
+ "type": "pointer",
562
+ "id": "finger1",
563
+ "parameters": {
564
+ "pointerType": "touch"
565
+ },
566
+ "actions": [
567
+ {
568
+ "type": "pointerMove",
569
+ "duration": 0,
570
+ "x": 540,
571
+ "y": 1900
572
+ },
573
+ {
574
+ "type": "pointerDown",
575
+ "button": 0
576
+ },
577
+ {
578
+ "type": "pointerMove",
579
+ "duration": 600,
580
+ "x": 540,
581
+ "y": 700
582
+ },
583
+ {
584
+ "type": "pointerUp",
585
+ "button": 0
586
+ }
587
+ ]
588
+ }
589
+ ]
590
+ }
591
+ ],
592
+ "returns": [
593
+ "value"
594
+ ],
595
+ "responses": {
596
+ "200": {
597
+ "description": "Gesture performed; value is null.",
598
+ "schema": {
599
+ "$schema": "NullValueResponse"
600
+ }
601
+ },
602
+ "400": {
603
+ "$response": "WebDriverError"
604
+ }
605
+ }
606
+ },
607
+ {
608
+ "name": "execute_script",
609
+ "method": "POST",
610
+ "path": "/wd/hub/session/{session_id}/execute/sync",
611
+ "mode": "write",
612
+ "entity": "app_session",
613
+ "intent": "Run an Appium mobile command or a BrowserStack session command \u2014 scroll a list, press a hardware key, launch or close an app, or mark the session passed or failed.",
614
+ "guidance": [
615
+ "Mark the result before ending the session: script 'browserstack_executor: {\"action\":\"setSessionStatus\",\"arguments\":{\"status\":\"passed\",\"reason\":\"Cart shows one item\"}}' with args []. status is passed or failed; a later call overwrites an earlier one, and the status survives delete_session.",
616
+ "Keep the setSessionStatus reason to one plain line under 256 characters with no escape sequences such as newlines.",
617
+ "Android scroll: script \"mobile: scrollGesture\" with args [{\"left\":100,\"top\":500,\"width\":880,\"height\":1500,\"direction\":\"down\",\"percent\":0.8}] \u2014 value true means more content remains below.",
618
+ "Which mobile: commands exist depends on the Appium version the session runs. The default Android version is Appium 1.x, which supports scrollGesture, swipeGesture, flingGesture, dragGesture, longClickGesture, doubleClickGesture and pinch gestures, but NOT mobile: pressKey \u2014 that needs a session started with appium_version 2.x or later.",
619
+ "An unsupported command fails with 404 \"unknown command\", and the message lists every mobile: command the session does support \u2014 read it rather than retrying variants."
620
+ ],
621
+ "path_params": [
622
+ {
623
+ "name": "session_id",
624
+ "type": "string",
625
+ "required": true,
626
+ "example": "0123456789abcdef0123456789abcdef01234567"
627
+ }
628
+ ],
629
+ "body": [
630
+ {
631
+ "name": "script",
632
+ "type": "string",
633
+ "required": true,
634
+ "description": "Either \"mobile: <command>\" or a string beginning \"browserstack_executor: \" followed by a JSON object with action and arguments.",
635
+ "example": "mobile: scrollGesture"
636
+ },
637
+ {
638
+ "name": "args",
639
+ "type": "array",
640
+ "required": true,
641
+ "description": "Arguments for the command \u2014 for mobile: commands a one-element array holding an options object; for browserstack_executor an empty array.",
642
+ "example": [
643
+ {
644
+ "left": 100,
645
+ "top": 500,
646
+ "width": 880,
647
+ "height": 1500,
648
+ "direction": "down",
649
+ "percent": 0.8
650
+ }
651
+ ]
652
+ }
653
+ ],
654
+ "returns": [
655
+ "value"
656
+ ],
657
+ "responses": {
658
+ "200": {
659
+ "description": "Command result; its shape depends on the command.",
660
+ "schema": {
661
+ "$schema": "AnyValueResponse"
662
+ }
663
+ },
664
+ "404": {
665
+ "$response": "WebDriverError"
666
+ },
667
+ "500": {
668
+ "$response": "WebDriverError"
669
+ }
670
+ }
671
+ },
672
+ {
673
+ "name": "get_window_rect",
674
+ "method": "GET",
675
+ "path": "/wd/hub/session/{session_id}/window/rect",
676
+ "mode": "read",
677
+ "entity": "app_session",
678
+ "intent": "Get the device screen size in pixels \u2014 use this to plan gesture coordinates, or as a cheap keep-alive so an idle session is not ended while deciding the next step.",
679
+ "guidance": [
680
+ "On Android the height excludes the system bars, so it can be smaller than a screenshot's height; page-source bounds use this same space."
681
+ ],
682
+ "path_params": [
683
+ {
684
+ "name": "session_id",
685
+ "type": "string",
686
+ "required": true,
687
+ "example": "0123456789abcdef0123456789abcdef01234567"
688
+ }
689
+ ],
690
+ "returns": [
691
+ "value.width",
692
+ "value.height",
693
+ "value.x",
694
+ "value.y"
695
+ ],
696
+ "responses": {
697
+ "200": {
698
+ "description": "Screen rectangle.",
699
+ "schema": {
700
+ "$schema": "RectResponse"
701
+ }
702
+ },
703
+ "404": {
704
+ "$response": "WebDriverError"
705
+ }
706
+ },
707
+ "disabled": true
708
+ },
709
+ {
710
+ "name": "delete_session",
711
+ "method": "DELETE",
712
+ "path": "/wd/hub/session/{session_id}",
713
+ "mode": "write",
714
+ "entity": "app_session",
715
+ "intent": "End a live App Automate session and release the device \u2014 use this when the flow is finished, after marking the session passed or failed.",
716
+ "guidance": [
717
+ "There is no way to resume an ended session; any further step needs create_session again, which reinstalls the app.",
718
+ "The session's video, logs and pass/fail status stay on the App Automate dashboard after it ends.",
719
+ "Call setSessionStatus through execute_script first \u2014 once the session has ended that route is gone."
720
+ ],
721
+ "path_params": [
722
+ {
723
+ "name": "session_id",
724
+ "type": "string",
725
+ "required": true,
726
+ "example": "0123456789abcdef0123456789abcdef01234567"
727
+ }
728
+ ],
729
+ "returns": [
730
+ "value"
731
+ ],
732
+ "responses": {
733
+ "200": {
734
+ "description": "Session ended; state is \"success\" and value is empty.",
735
+ "schema": {
736
+ "type": "object",
737
+ "properties": {
738
+ "state": {
739
+ "type": "string",
740
+ "description": "\"success\" when the session was ended."
741
+ },
742
+ "value": {
743
+ "type": "string",
744
+ "description": "Empty string."
745
+ }
746
+ }
747
+ }
748
+ },
749
+ "404": {
750
+ "$response": "WebDriverError"
751
+ }
752
+ }
753
+ },
754
+ {
755
+ "name": "list_devices",
756
+ "method": "GET",
757
+ "path": "/app-automate/devices.json",
758
+ "mode": "read",
759
+ "entity": "device",
760
+ "intent": "List the real Android and iOS devices this account can run an app session on, with the exact device names and OS versions a session accepts.",
761
+ "guidance": [
762
+ "The device and os_version strings must be passed to create_session exactly as they appear here; a near miss is rejected rather than substituted.",
763
+ "One model appears once per OS version it is offered on, so the same device name repeats across rows.",
764
+ "This is what the account is entitled to, not what is free right now \u2014 a listed device can still be busy when a session starts.",
765
+ "There is no filtering on the wire: the whole catalogue comes back and the caller narrows it.",
766
+ "realMobile is true for physical devices and absent or false for emulators."
767
+ ],
768
+ "returns": [
769
+ "device",
770
+ "os",
771
+ "os_version",
772
+ "device_tier",
773
+ "device_limit",
774
+ "realMobile"
775
+ ],
776
+ "responses": {
777
+ "200": {
778
+ "description": "Every device the account can use.",
779
+ "schema": {
780
+ "$schema": "DeviceList"
781
+ }
782
+ },
783
+ "401": {
784
+ "$response": "Unauthorized"
785
+ }
786
+ },
787
+ "host": "api"
788
+ },
789
+ {
790
+ "name": "list_dedicated_devices",
791
+ "method": "GET",
792
+ "path": "/app-automate/dedicated_devices.json",
793
+ "mode": "read",
794
+ "entity": "device",
795
+ "intent": "List the account's private (dedicated) devices \u2014 use this to find devices reserved for this team rather than shared with other customers.",
796
+ "guidance": [
797
+ "An account without a private device cloud gets an empty array, which is an answer and not an error.",
798
+ "Rows come back as the product sends them and carry more fields than the public catalogue; read the keys rather than assuming the public shape.",
799
+ "Private devices are the only ones a device tunnel can be opened on."
800
+ ],
801
+ "returns": [
802
+ "device",
803
+ "os",
804
+ "os_version"
805
+ ],
806
+ "responses": {
807
+ "200": {
808
+ "description": "The account's private devices, or an empty array.",
809
+ "schema": {
810
+ "$schema": "DedicatedDeviceList"
811
+ }
812
+ },
813
+ "401": {
814
+ "$response": "Unauthorized"
815
+ }
816
+ },
817
+ "host": "api"
818
+ },
819
+ {
820
+ "name": "get_plan",
821
+ "method": "GET",
822
+ "path": "/app-automate/plan.json",
823
+ "mode": "read",
824
+ "entity": "plan",
825
+ "intent": "Show the App Automate plan and how many parallel sessions are running, queued and allowed \u2014 check this before starting a session or a batch of them.",
826
+ "guidance": [
827
+ "A session start blocks and then queues when parallel_sessions_running has reached parallel_sessions_max_allowed, so a start that seems slow is usually this.",
828
+ "team_parallel_sessions_max_allowed is the ceiling shared across everyone on the team and can be lower than the account's own maximum.",
829
+ "The counts are account-wide and include sessions other people and CI started, not only this caller's."
830
+ ],
831
+ "returns": [
832
+ "automate_plan",
833
+ "parallel_sessions_running",
834
+ "parallel_sessions_max_allowed",
835
+ "team_parallel_sessions_max_allowed",
836
+ "queued_sessions",
837
+ "queued_sessions_max_allowed",
838
+ "terminal_access"
839
+ ],
840
+ "responses": {
841
+ "200": {
842
+ "description": "Plan name and parallel usage.",
843
+ "schema": {
844
+ "$schema": "Plan"
845
+ }
846
+ },
847
+ "401": {
848
+ "$response": "Unauthorized"
849
+ }
850
+ },
851
+ "host": "api"
852
+ },
853
+ {
854
+ "name": "upload_app_build",
855
+ "method": "POST",
856
+ "path": "/app-automate/upload",
857
+ "mode": "write",
858
+ "entity": "app_build",
859
+ "intent": "Upload an app build to App Automate from a public download link and get back the bs:// URL a session installs \u2014 use this to make a new .apk, .aab or .ipa available to devices.",
860
+ "guidance": [
861
+ "Only the download-link form can be used here. The same endpoint also accepts a multipart upload carrying the file itself, which this surface cannot send \u2014 so for a build that exists only on the caller's own machine, give them that multipart request to run themselves and take the bs:// url it returns. There is no way to hand the bytes over from here.",
862
+ "The url must be https and reachable by BrowserStack without credentials; a link behind a login fails on their side, not the caller's.",
863
+ "Pass custom_id to give the build a stable name, then later sessions can use that name in place of the bs:// URL and always get the newest build under it.",
864
+ "Re-uploading under the same custom_id adds a new build rather than replacing the old one; both stay listed.",
865
+ "Builds expire after 30 days, so a bs:// URL held from an earlier run can stop working.",
866
+ "Uploading a large build can take minutes \u2014 the call does not return until BrowserStack has fetched the whole file.",
867
+ "upload_app_build_from_file takes a build that is on this machine instead, where the server runs beside the user. It is not offered on a remote server, where a path would name the operator's disk rather than theirs."
868
+ ],
869
+ "body": [
870
+ {
871
+ "name": "url",
872
+ "type": "string",
873
+ "required": true,
874
+ "format": "uri",
875
+ "description": "Public https link BrowserStack downloads the build from.",
876
+ "example": "https://example.com/builds/app-release.apk"
877
+ },
878
+ {
879
+ "name": "custom_id",
880
+ "type": "string",
881
+ "maxLength": 100,
882
+ "description": "Stable name for the build, reusable in place of the bs:// URL.",
883
+ "example": "my-app-staging"
884
+ }
885
+ ],
886
+ "returns": [
887
+ "app_url",
888
+ "custom_id",
889
+ "shareable_id"
890
+ ],
891
+ "responses": {
892
+ "200": {
893
+ "description": "The uploaded build.",
894
+ "schema": {
895
+ "$schema": "AppUpload"
896
+ }
897
+ },
898
+ "401": {
899
+ "$response": "Unauthorized"
900
+ }
901
+ },
902
+ "host": "api"
903
+ },
904
+ {
905
+ "name": "list_app_builds",
906
+ "method": "GET",
907
+ "path": "/app-automate/recent_apps",
908
+ "mode": "read",
909
+ "entity": "app_build",
910
+ "intent": "List the app builds this user has uploaded, newest first, with their bs:// URLs \u2014 use this to reuse an existing build instead of uploading again.",
911
+ "guidance": [
912
+ "An account with no uploads answers with a message object or a 404 rather than an empty array; treat both as 'nothing uploaded yet'.",
913
+ "This is only the calling user's own uploads \u2014 use list_team_app_builds for everyone on the team.",
914
+ "app_url is what create_session takes; app_id is an internal identifier and is not accepted there."
915
+ ],
916
+ "query": [
917
+ {
918
+ "name": "limit",
919
+ "type": "integer",
920
+ "minimum": 1,
921
+ "maximum": 100,
922
+ "example": 20,
923
+ "description": "How many builds to return."
924
+ }
925
+ ],
926
+ "returns": [
927
+ "app_name",
928
+ "app_version",
929
+ "app_url",
930
+ "app_id",
931
+ "custom_id",
932
+ "uploaded_at",
933
+ "shareable_id"
934
+ ],
935
+ "responses": {
936
+ "200": {
937
+ "description": "Recently uploaded builds, newest first.",
938
+ "schema": {
939
+ "$schema": "AppBuildList"
940
+ }
941
+ },
942
+ "401": {
943
+ "$response": "Unauthorized"
944
+ }
945
+ },
946
+ "host": "api"
947
+ },
948
+ {
949
+ "name": "list_team_app_builds",
950
+ "method": "GET",
951
+ "path": "/app-automate/recent_group_apps",
952
+ "mode": "read",
953
+ "entity": "app_build",
954
+ "intent": "List app builds uploaded by anyone on the team, newest first \u2014 use this to find a colleague's or CI's build rather than only your own.",
955
+ "guidance": [
956
+ "Same rows as list_app_builds, widened to the whole team; a build you uploaded appears in both.",
957
+ "An empty team history answers with a message object or a 404 rather than an empty array."
958
+ ],
959
+ "query": [
960
+ {
961
+ "name": "limit",
962
+ "type": "integer",
963
+ "minimum": 1,
964
+ "maximum": 100,
965
+ "example": 20,
966
+ "description": "How many builds to return."
967
+ }
968
+ ],
969
+ "returns": [
970
+ "app_name",
971
+ "app_version",
972
+ "app_url",
973
+ "app_id",
974
+ "custom_id",
975
+ "uploaded_at",
976
+ "shareable_id"
977
+ ],
978
+ "responses": {
979
+ "200": {
980
+ "description": "The team's recently uploaded builds.",
981
+ "schema": {
982
+ "$schema": "AppBuildList"
983
+ }
984
+ },
985
+ "401": {
986
+ "$response": "Unauthorized"
987
+ }
988
+ },
989
+ "host": "api"
990
+ },
991
+ {
992
+ "name": "get_app_builds_by_custom_id",
993
+ "method": "GET",
994
+ "path": "/app-automate/recent_apps/{custom_id}",
995
+ "mode": "read",
996
+ "entity": "app_build",
997
+ "intent": "List every build uploaded under one custom id, newest first \u2014 use this to resolve a stable build name to the bs:// URL of its latest version.",
998
+ "guidance": [
999
+ "The newest build under the id is the first row; passing the custom id itself to create_session has the same effect and is usually simpler.",
1000
+ "An unknown custom id answers with a message object or a 404, not an empty array."
1001
+ ],
1002
+ "path_params": [
1003
+ {
1004
+ "name": "custom_id",
1005
+ "type": "string",
1006
+ "required": true,
1007
+ "description": "The stable name a build was uploaded under.",
1008
+ "example": "my-app-staging"
1009
+ }
1010
+ ],
1011
+ "returns": [
1012
+ "app_name",
1013
+ "app_version",
1014
+ "app_url",
1015
+ "app_id",
1016
+ "custom_id",
1017
+ "uploaded_at",
1018
+ "shareable_id"
1019
+ ],
1020
+ "responses": {
1021
+ "200": {
1022
+ "description": "Builds under that custom id.",
1023
+ "schema": {
1024
+ "$schema": "AppBuildList"
1025
+ }
1026
+ },
1027
+ "401": {
1028
+ "$response": "Unauthorized"
1029
+ }
1030
+ },
1031
+ "host": "api"
1032
+ },
1033
+ {
1034
+ "name": "get_session",
1035
+ "method": "GET",
1036
+ "path": "/app-automate/sessions/{session_id}.json",
1037
+ "mode": "read",
1038
+ "entity": "app_session",
1039
+ "intent": "Get a session's record: its status, device, duration, the shareable public link, the video, and which app build ran \u2014 use this for the links to share or to check how a finished session ended.",
1040
+ "guidance": [
1041
+ "browser_url is the session on the dashboard and needs a BrowserStack login \u2014 this is the link to give someone on the team who wants to watch or review the run.",
1042
+ "public_url opens without any login, so it is the one to send outside the team; it carries an auth token, so treat it as a secret and do not paste it anywhere public.",
1043
+ "video_url is only complete once the session has ended; during a run it may be missing or still filling.",
1044
+ "This works for any session on the account, including ones this caller did not start.",
1045
+ "status is the status that was set on the session; browserstack_status is what BrowserStack itself observed, and the two can disagree when a run was marked by hand.",
1046
+ "app_details carries the build that ran, including its custom id, which is how to tell which version a past session used."
1047
+ ],
1048
+ "path_params": [
1049
+ {
1050
+ "name": "session_id",
1051
+ "type": "string",
1052
+ "required": true,
1053
+ "example": "0123456789abcdef0123456789abcdef01234567"
1054
+ }
1055
+ ],
1056
+ "returns": [
1057
+ "status",
1058
+ "reason",
1059
+ "device",
1060
+ "os",
1061
+ "os_version",
1062
+ "duration",
1063
+ "browser_url",
1064
+ "public_url",
1065
+ "video_url",
1066
+ "build_name",
1067
+ "project_name",
1068
+ "created_at",
1069
+ "app_details",
1070
+ "browserstack_status",
1071
+ "hashed_id",
1072
+ "name"
1073
+ ],
1074
+ "responses": {
1075
+ "200": {
1076
+ "description": "The session record.",
1077
+ "schema": {
1078
+ "$schema": "SessionDetails"
1079
+ }
1080
+ },
1081
+ "401": {
1082
+ "$response": "Unauthorized"
1083
+ }
1084
+ },
1085
+ "host": "api"
1086
+ },
1087
+ {
1088
+ "name": "set_session_status",
1089
+ "method": "PUT",
1090
+ "path": "/app-automate/sessions/{session_id}.json",
1091
+ "mode": "write",
1092
+ "entity": "app_session",
1093
+ "intent": "Mark a session passed or failed with a reason, so the dashboard shows the outcome \u2014 use this to record the verdict of what was driven on the device.",
1094
+ "guidance": [
1095
+ "Works after the session has ended as well as during it, so the verdict can be set once the result is known.",
1096
+ "Only 'passed' and 'failed' are accepted; any other value is rejected with a 422.",
1097
+ "reason is truncated at 255 characters.",
1098
+ "An unset session shows as neither passed nor failed on the dashboard, which reads as an abandoned run.",
1099
+ "The same thing can be done from inside a running session with execute_script and the browserstack_executor setSessionStatus action; this way needs no open session."
1100
+ ],
1101
+ "path_params": [
1102
+ {
1103
+ "name": "session_id",
1104
+ "type": "string",
1105
+ "required": true,
1106
+ "example": "0123456789abcdef0123456789abcdef01234567"
1107
+ }
1108
+ ],
1109
+ "body": [
1110
+ {
1111
+ "name": "status",
1112
+ "type": "string",
1113
+ "required": true,
1114
+ "values": [
1115
+ "passed",
1116
+ "failed"
1117
+ ],
1118
+ "example": "passed"
1119
+ },
1120
+ {
1121
+ "name": "reason",
1122
+ "type": "string",
1123
+ "maxLength": 255,
1124
+ "description": "Short explanation shown beside the status.",
1125
+ "example": "Checkout completed and the cart showed the right total"
1126
+ }
1127
+ ],
1128
+ "returns": [
1129
+ "status",
1130
+ "reason",
1131
+ "hashed_id",
1132
+ "name",
1133
+ "device",
1134
+ "os",
1135
+ "os_version",
1136
+ "build_name",
1137
+ "project_name"
1138
+ ],
1139
+ "responses": {
1140
+ "200": {
1141
+ "description": "The updated session record.",
1142
+ "schema": {
1143
+ "$schema": "SessionStatusUpdate"
1144
+ }
1145
+ },
1146
+ "401": {
1147
+ "$response": "Unauthorized"
1148
+ }
1149
+ },
1150
+ "host": "api"
1151
+ },
1152
+ {
1153
+ "name": "set_session_network",
1154
+ "method": "PUT",
1155
+ "path": "/app-automate/sessions/{session_id}/update_network.json",
1156
+ "mode": "write",
1157
+ "entity": "app_session",
1158
+ "intent": "Change the network conditions of a running session to a named profile \u2014 use this to see how the app behaves on a slow or absent connection.",
1159
+ "guidance": [
1160
+ "Mid-session changes take a named profile only; custom bandwidth or latency values are rejected with a 422.",
1161
+ "'reset' restores the device's normal connection.",
1162
+ "The session must still be running; this cannot be applied to a finished one.",
1163
+ "To start a session already under a profile, pass network_profile to create_session instead."
1164
+ ],
1165
+ "path_params": [
1166
+ {
1167
+ "name": "session_id",
1168
+ "type": "string",
1169
+ "required": true,
1170
+ "example": "0123456789abcdef0123456789abcdef01234567"
1171
+ }
1172
+ ],
1173
+ "body": [
1174
+ {
1175
+ "name": "networkProfile",
1176
+ "type": "string",
1177
+ "required": true,
1178
+ "values": [
1179
+ "no-network",
1180
+ "2g-gprs-good",
1181
+ "2g-gprs-lossy",
1182
+ "3g-umts-good",
1183
+ "3g-umts-lossy",
1184
+ "4g-lte-good",
1185
+ "4g-lte-advanced-good",
1186
+ "4g-lte-advanced-lossy",
1187
+ "reset"
1188
+ ],
1189
+ "example": "3g-umts-good"
1190
+ }
1191
+ ],
1192
+ "returns": [
1193
+ "message"
1194
+ ],
1195
+ "responses": {
1196
+ "200": {
1197
+ "description": "Acknowledgement that the profile was applied.",
1198
+ "schema": {
1199
+ "$schema": "MessageResponse"
1200
+ }
1201
+ },
1202
+ "401": {
1203
+ "$response": "Unauthorized"
1204
+ }
1205
+ },
1206
+ "host": "api",
1207
+ "disabled": true
1208
+ },
1209
+ {
1210
+ "name": "take_screenshot",
1211
+ "method": "GET",
1212
+ "path": "/wd/hub/session/{session_id}/screenshot",
1213
+ "mode": "read",
1214
+ "entity": "app_session",
1215
+ "intent": "Take a screenshot of what is on the device right now, as a base64 PNG \u2014 use this to see the screen as a user would, including anything the UI tree does not describe.",
1216
+ "guidance": [
1217
+ "The image is returned inline as base64 in value and a phone screen runs to hundreds of kilobytes, so fetch it only when the picture is needed and prefer get_page_source for deciding what to act on.",
1218
+ "Screenshot pixels are the device's full resolution and can be taller than the rectangle get_window_rect reports, because that excludes the system bars; scale before reusing screenshot coordinates for gestures.",
1219
+ "A screen the app marks secure comes back black, which is the app's doing and not a failure."
1220
+ ],
1221
+ "path_params": [
1222
+ {
1223
+ "name": "session_id",
1224
+ "type": "string",
1225
+ "required": true,
1226
+ "example": "0123456789abcdef0123456789abcdef01234567"
1227
+ }
1228
+ ],
1229
+ "returns": [
1230
+ "value"
1231
+ ],
1232
+ "responses": {
1233
+ "200": {
1234
+ "description": "Base64-encoded PNG of the screen.",
1235
+ "schema": {
1236
+ "$schema": "StringValueResponse"
1237
+ }
1238
+ },
1239
+ "404": {
1240
+ "$response": "WebDriverError"
1241
+ }
1242
+ }
1243
+ },
1244
+ {
1245
+ "name": "press_back",
1246
+ "method": "POST",
1247
+ "path": "/wd/hub/session/{session_id}/back",
1248
+ "mode": "write",
1249
+ "entity": "app_session",
1250
+ "intent": "Go back one screen \u2014 use this to leave a detail page, close a dialog, or undo a navigation step.",
1251
+ "guidance": [
1252
+ "On Android this is the system back button and is the reliable way back.",
1253
+ "On iOS this works on a screen that uses a standard navigation bar, where it acts on the bar's own back control. A screen that draws its own header instead is left where it was, and the call still answers 200 \u2014 so confirm from the page source rather than from the status.",
1254
+ "Pressing back on the first screen of an app can send the app to the background and show the home screen; read the page source afterwards to confirm where you are.",
1255
+ "The response is null on success and says nothing about whether the screen actually changed.",
1256
+ "When an iOS screen ignores it, swipe in from the left edge with perform_actions: start at exactly x=0 and move in several short steps. One long move, or a start at x=2, is ignored."
1257
+ ],
1258
+ "path_params": [
1259
+ {
1260
+ "name": "session_id",
1261
+ "type": "string",
1262
+ "required": true,
1263
+ "example": "0123456789abcdef0123456789abcdef01234567"
1264
+ }
1265
+ ],
1266
+ "returns": [
1267
+ "value"
1268
+ ],
1269
+ "responses": {
1270
+ "200": {
1271
+ "description": "Null on success.",
1272
+ "schema": {
1273
+ "$schema": "NullValueResponse"
1274
+ }
1275
+ },
1276
+ "404": {
1277
+ "$response": "WebDriverError"
1278
+ }
1279
+ }
1280
+ },
1281
+ {
1282
+ "name": "get_orientation",
1283
+ "method": "GET",
1284
+ "path": "/wd/hub/session/{session_id}/orientation",
1285
+ "mode": "read",
1286
+ "entity": "app_session",
1287
+ "intent": "Read whether the device is currently portrait or landscape.",
1288
+ "guidance": [
1289
+ "The value is upper case, PORTRAIT or LANDSCAPE."
1290
+ ],
1291
+ "path_params": [
1292
+ {
1293
+ "name": "session_id",
1294
+ "type": "string",
1295
+ "required": true,
1296
+ "example": "0123456789abcdef0123456789abcdef01234567"
1297
+ }
1298
+ ],
1299
+ "returns": [
1300
+ "value"
1301
+ ],
1302
+ "responses": {
1303
+ "200": {
1304
+ "description": "The current orientation.",
1305
+ "schema": {
1306
+ "$schema": "StringValueResponse"
1307
+ }
1308
+ },
1309
+ "404": {
1310
+ "$response": "WebDriverError"
1311
+ }
1312
+ }
1313
+ },
1314
+ {
1315
+ "name": "set_orientation",
1316
+ "method": "POST",
1317
+ "path": "/wd/hub/session/{session_id}/orientation",
1318
+ "mode": "write",
1319
+ "entity": "app_session",
1320
+ "intent": "Rotate the device to portrait or landscape \u2014 use this to test a layout in the other orientation.",
1321
+ "guidance": [
1322
+ "An app that locks its orientation refuses the rotation with a 400 'invalid element state' saying the screen could not be changed; that is the app's choice, not a broken call.",
1323
+ "The value must be upper case, PORTRAIT or LANDSCAPE.",
1324
+ "Element positions and the window rectangle change after a rotation, so re-read the page source before acting on coordinates.",
1325
+ "The response carries the new orientation on Android and null on iOS, so treat a 200 rather than the value as the sign it worked, and read get_orientation if the value matters."
1326
+ ],
1327
+ "path_params": [
1328
+ {
1329
+ "name": "session_id",
1330
+ "type": "string",
1331
+ "required": true,
1332
+ "example": "0123456789abcdef0123456789abcdef01234567"
1333
+ }
1334
+ ],
1335
+ "body": [
1336
+ {
1337
+ "name": "orientation",
1338
+ "type": "string",
1339
+ "required": true,
1340
+ "values": [
1341
+ "PORTRAIT",
1342
+ "LANDSCAPE"
1343
+ ],
1344
+ "example": "LANDSCAPE"
1345
+ }
1346
+ ],
1347
+ "returns": [
1348
+ "value"
1349
+ ],
1350
+ "responses": {
1351
+ "200": {
1352
+ "description": "The orientation now in effect.",
1353
+ "schema": {
1354
+ "$schema": "StringValueResponse"
1355
+ }
1356
+ },
1357
+ "404": {
1358
+ "$response": "WebDriverError"
1359
+ }
1360
+ }
1361
+ },
1362
+ {
1363
+ "name": "list_contexts",
1364
+ "method": "GET",
1365
+ "path": "/wd/hub/session/{session_id}/contexts",
1366
+ "mode": "read",
1367
+ "entity": "app_session",
1368
+ "intent": "List the contexts available on screen \u2014 the native app and any web views \u2014 so a hybrid app's web content can be driven.",
1369
+ "guidance": [
1370
+ "NATIVE_APP is always present; a web view appears as a WEBVIEW_ prefixed name only while one is actually on screen.",
1371
+ "A screen with no web view returns NATIVE_APP alone, which means the content is native and not that something failed."
1372
+ ],
1373
+ "path_params": [
1374
+ {
1375
+ "name": "session_id",
1376
+ "type": "string",
1377
+ "required": true,
1378
+ "example": "0123456789abcdef0123456789abcdef01234567"
1379
+ }
1380
+ ],
1381
+ "returns": [
1382
+ "value"
1383
+ ],
1384
+ "responses": {
1385
+ "200": {
1386
+ "description": "The available context names.",
1387
+ "schema": {
1388
+ "$schema": "StringListResponse"
1389
+ }
1390
+ },
1391
+ "404": {
1392
+ "$response": "WebDriverError"
1393
+ }
1394
+ }
1395
+ },
1396
+ {
1397
+ "name": "set_context",
1398
+ "method": "POST",
1399
+ "path": "/wd/hub/session/{session_id}/context",
1400
+ "mode": "write",
1401
+ "entity": "app_session",
1402
+ "intent": "Switch between the native app and a web view \u2014 use this when the part of the screen you need to drive is web content inside the app.",
1403
+ "guidance": [
1404
+ "Use a name exactly as list_contexts returned it; an invented WEBVIEW name is rejected.",
1405
+ "In a web view, selectors change: find_element then takes css selector and the page source is HTML rather than the native UI tree.",
1406
+ "Switch back to NATIVE_APP before driving native controls again.",
1407
+ "A web view's name carries the process id of the app that owns it, so it differs between sessions and after the app restarts. Read it from list_contexts each time rather than reusing one seen earlier."
1408
+ ],
1409
+ "path_params": [
1410
+ {
1411
+ "name": "session_id",
1412
+ "type": "string",
1413
+ "required": true,
1414
+ "example": "0123456789abcdef0123456789abcdef01234567"
1415
+ }
1416
+ ],
1417
+ "body": [
1418
+ {
1419
+ "name": "name",
1420
+ "type": "string",
1421
+ "required": true,
1422
+ "description": "Context name from list_contexts.",
1423
+ "example": "NATIVE_APP"
1424
+ }
1425
+ ],
1426
+ "returns": [
1427
+ "value"
1428
+ ],
1429
+ "responses": {
1430
+ "200": {
1431
+ "description": "Null on success.",
1432
+ "schema": {
1433
+ "$schema": "NullValueResponse"
1434
+ }
1435
+ },
1436
+ "404": {
1437
+ "$response": "WebDriverError"
1438
+ }
1439
+ }
1440
+ },
1441
+ {
1442
+ "name": "hide_keyboard",
1443
+ "method": "POST",
1444
+ "path": "/wd/hub/session/{session_id}/appium/device/hide_keyboard",
1445
+ "mode": "write",
1446
+ "entity": "app_session",
1447
+ "intent": "Close the on-screen keyboard \u2014 use this when it covers a button or field you need to tap after typing.",
1448
+ "guidance": [
1449
+ "The response is true when a keyboard was dismissed and false when none was showing; false is not an error.",
1450
+ "On iOS this can be refused by a keyboard the app controls; the fallback is tapping an empty area or the keyboard's own Done key.",
1451
+ "A tap aimed at a control hidden behind the keyboard lands on the keyboard instead and reports success, so close it first rather than afterwards.",
1452
+ "On iOS this frequently leaves the keyboard open while still answering successfully, so always confirm with is_keyboard_shown. Two things do close it there: send_keys with a newline, or execute_script with 'mobile: hideKeyboard' and args [{\"keys\":[\"return\"]}]. Tapping an empty area of the screen does not, and neither does that command with no keys or with [\"Done\"], which fail as \"invalid element state\".",
1453
+ "is_keyboard_shown reports the state this changes, and is the reliable way to tell whether the keyboard actually went away."
1454
+ ],
1455
+ "path_params": [
1456
+ {
1457
+ "name": "session_id",
1458
+ "type": "string",
1459
+ "required": true,
1460
+ "example": "0123456789abcdef0123456789abcdef01234567"
1461
+ }
1462
+ ],
1463
+ "returns": [
1464
+ "value"
1465
+ ],
1466
+ "responses": {
1467
+ "200": {
1468
+ "description": "True when a keyboard was dismissed.",
1469
+ "schema": {
1470
+ "$schema": "BooleanValueResponse"
1471
+ }
1472
+ },
1473
+ "404": {
1474
+ "$response": "WebDriverError"
1475
+ }
1476
+ }
1477
+ },
1478
+ {
1479
+ "name": "set_geo_location",
1480
+ "method": "POST",
1481
+ "path": "/wd/hub/session/{session_id}/location",
1482
+ "mode": "write",
1483
+ "entity": "app_session",
1484
+ "intent": "Move the device's simulated GPS position \u2014 use this to test location-dependent behaviour without starting a new session.",
1485
+ "guidance": [
1486
+ "The response is a position reading that does not reliably reflect the value just sent: on Android it has come back as the device's previous location. Confirm it afterwards \u2014 with get_geo_location on Android, or get_simulated_location on iOS.",
1487
+ "latitude, longitude and altitude must be nested under a location object.",
1488
+ "altitude is required by the wire format even when it does not matter; send 0.",
1489
+ "The app may cache its last fix, so a change can need the app to request location again before it is visible.",
1490
+ "Latitude and longitude are applied as sent; altitude is not preserved and reads back as 0, so do not rely on it."
1491
+ ],
1492
+ "path_params": [
1493
+ {
1494
+ "name": "session_id",
1495
+ "type": "string",
1496
+ "required": true,
1497
+ "example": "0123456789abcdef0123456789abcdef01234567"
1498
+ }
1499
+ ],
1500
+ "body": [
1501
+ {
1502
+ "name": "location",
1503
+ "type": "object",
1504
+ "required": true,
1505
+ "description": "The position to move to.",
1506
+ "fields": [
1507
+ {
1508
+ "name": "latitude",
1509
+ "type": "number",
1510
+ "required": true,
1511
+ "minimum": -90,
1512
+ "maximum": 90,
1513
+ "example": 48.8566
1514
+ },
1515
+ {
1516
+ "name": "longitude",
1517
+ "type": "number",
1518
+ "required": true,
1519
+ "minimum": -180,
1520
+ "maximum": 180,
1521
+ "example": 2.3522
1522
+ },
1523
+ {
1524
+ "name": "altitude",
1525
+ "type": "number",
1526
+ "required": true,
1527
+ "example": 0
1528
+ }
1529
+ ]
1530
+ }
1531
+ ],
1532
+ "returns": [
1533
+ "value"
1534
+ ],
1535
+ "responses": {
1536
+ "200": {
1537
+ "description": "A position reading, which may be the location before the change rather than after it.",
1538
+ "schema": {
1539
+ "$schema": "LocationResponse"
1540
+ }
1541
+ },
1542
+ "404": {
1543
+ "$response": "WebDriverError"
1544
+ }
1545
+ },
1546
+ "disabled": true
1547
+ },
1548
+ {
1549
+ "name": "get_geo_location",
1550
+ "method": "GET",
1551
+ "path": "/wd/hub/session/{session_id}/location",
1552
+ "mode": "read",
1553
+ "entity": "app_session",
1554
+ "intent": "Read the device's current simulated GPS position \u2014 use this to confirm a location change took effect.",
1555
+ "guidance": [
1556
+ "Android only \u2014 iOS answers 500, saying the command is not supported. Read the position on iOS with get_simulated_location instead.",
1557
+ "On Android this is the authoritative reading, and worth making when set_geo_location's own response is in doubt."
1558
+ ],
1559
+ "path_params": [
1560
+ {
1561
+ "name": "session_id",
1562
+ "type": "string",
1563
+ "required": true,
1564
+ "example": "0123456789abcdef0123456789abcdef01234567"
1565
+ }
1566
+ ],
1567
+ "returns": [
1568
+ "value"
1569
+ ],
1570
+ "responses": {
1571
+ "200": {
1572
+ "description": "The current position.",
1573
+ "schema": {
1574
+ "$schema": "LocationResponse"
1575
+ }
1576
+ },
1577
+ "404": {
1578
+ "$response": "WebDriverError"
1579
+ }
1580
+ },
1581
+ "disabled": true
1582
+ },
1583
+ {
1584
+ "name": "push_file",
1585
+ "method": "POST",
1586
+ "path": "/wd/hub/session/{session_id}/appium/device/push_file",
1587
+ "mode": "write",
1588
+ "entity": "app_session",
1589
+ "intent": "Write a file onto the device from base64 content \u2014 use this to put test data, a fixture or a document where the app will read it.",
1590
+ "guidance": [
1591
+ "data must be base64; to push text, encode it first. A file on the caller's own disk cannot be sent as a path, only as its encoded content.",
1592
+ "Android takes an absolute path such as /sdcard/Download/data.json.",
1593
+ "iOS takes an app-container path in the form @bundle.id:documents/data.json and can only write inside apps that allow it.",
1594
+ "The file lasts only as long as the session; the device is wiped afterwards.",
1595
+ "The response is null on success and does not confirm the file is readable by the app."
1596
+ ],
1597
+ "path_params": [
1598
+ {
1599
+ "name": "session_id",
1600
+ "type": "string",
1601
+ "required": true,
1602
+ "example": "0123456789abcdef0123456789abcdef01234567"
1603
+ }
1604
+ ],
1605
+ "body": [
1606
+ {
1607
+ "name": "path",
1608
+ "type": "string",
1609
+ "required": true,
1610
+ "description": "Destination path on the device.",
1611
+ "example": "/sdcard/Download/data.json"
1612
+ },
1613
+ {
1614
+ "name": "data",
1615
+ "type": "string",
1616
+ "required": true,
1617
+ "description": "Base64-encoded file content.",
1618
+ "example": "aGVsbG8="
1619
+ }
1620
+ ],
1621
+ "returns": [
1622
+ "value"
1623
+ ],
1624
+ "responses": {
1625
+ "200": {
1626
+ "description": "Null on success.",
1627
+ "schema": {
1628
+ "$schema": "NullValueResponse"
1629
+ }
1630
+ },
1631
+ "404": {
1632
+ "$response": "WebDriverError"
1633
+ }
1634
+ },
1635
+ "disabled": true
1636
+ },
1637
+ {
1638
+ "name": "pull_file",
1639
+ "method": "POST",
1640
+ "path": "/wd/hub/session/{session_id}/appium/device/pull_file",
1641
+ "mode": "read",
1642
+ "entity": "app_session",
1643
+ "intent": "Read a file off the device as base64 \u2014 use this to check what the app wrote, such as a log, an export or a saved document.",
1644
+ "guidance": [
1645
+ "The content comes back base64-encoded in value and must be decoded; a large file is returned whole, so pull only what is needed.",
1646
+ "Paths are the same as for push_file: absolute on Android, @bundle.id:documents/... on iOS.",
1647
+ "A missing path is a 404 rather than empty content.",
1648
+ "This is POST because the path travels in the body; it changes nothing on the device."
1649
+ ],
1650
+ "path_params": [
1651
+ {
1652
+ "name": "session_id",
1653
+ "type": "string",
1654
+ "required": true,
1655
+ "example": "0123456789abcdef0123456789abcdef01234567"
1656
+ }
1657
+ ],
1658
+ "body": [
1659
+ {
1660
+ "name": "path",
1661
+ "type": "string",
1662
+ "required": true,
1663
+ "description": "Path to read on the device.",
1664
+ "example": "/sdcard/Download/data.json"
1665
+ }
1666
+ ],
1667
+ "returns": [
1668
+ "value"
1669
+ ],
1670
+ "responses": {
1671
+ "200": {
1672
+ "description": "Base64-encoded file content.",
1673
+ "schema": {
1674
+ "$schema": "StringValueResponse"
1675
+ }
1676
+ },
1677
+ "404": {
1678
+ "$response": "WebDriverError"
1679
+ }
1680
+ },
1681
+ "disabled": true
1682
+ },
1683
+ {
1684
+ "name": "press_keycode",
1685
+ "method": "POST",
1686
+ "path": "/wd/hub/session/{session_id}/appium/device/press_keycode",
1687
+ "mode": "write",
1688
+ "entity": "app_session",
1689
+ "intent": "Press a hardware or system key on an Android device by its key code \u2014 use this for home, enter, delete, volume or any key with no on-screen control.",
1690
+ "guidance": [
1691
+ "Android only. iOS has no equivalent: use press_back to go back, and execute_script with 'mobile: pressButton' for home and the volume keys.",
1692
+ "Common codes: 3 home, 4 back, 66 enter, 67 delete, 61 tab, 82 menu, 84 search, 187 app switch, 24 volume up, 25 volume down, 26 power.",
1693
+ "press_back is the clearer way to send 4.",
1694
+ "The response is null on success and says nothing about what the key did.",
1695
+ "Called on iOS this is a 405 \"unknown method\" rather than a silent no-op; use press_back or 'mobile: pressButton' there."
1696
+ ],
1697
+ "path_params": [
1698
+ {
1699
+ "name": "session_id",
1700
+ "type": "string",
1701
+ "required": true,
1702
+ "example": "0123456789abcdef0123456789abcdef01234567"
1703
+ }
1704
+ ],
1705
+ "body": [
1706
+ {
1707
+ "name": "keycode",
1708
+ "type": "integer",
1709
+ "required": true,
1710
+ "minimum": 0,
1711
+ "maximum": 400,
1712
+ "description": "Android KeyEvent code.",
1713
+ "example": 66
1714
+ },
1715
+ {
1716
+ "name": "metastate",
1717
+ "type": "integer",
1718
+ "minimum": 0,
1719
+ "description": "Modifier bitmask, e.g. 1 for shift.",
1720
+ "example": 1
1721
+ }
1722
+ ],
1723
+ "returns": [
1724
+ "value"
1725
+ ],
1726
+ "responses": {
1727
+ "200": {
1728
+ "description": "Null on success.",
1729
+ "schema": {
1730
+ "$schema": "NullValueResponse"
1731
+ }
1732
+ },
1733
+ "404": {
1734
+ "$response": "WebDriverError"
1735
+ },
1736
+ "405": {
1737
+ "description": "The command is not implemented on this platform, as \"unknown method\".",
1738
+ "$response": "WebDriverError"
1739
+ }
1740
+ }
1741
+ },
1742
+ {
1743
+ "name": "get_current_app",
1744
+ "method": "POST",
1745
+ "path": "/wd/hub/session/{session_id}/execute/sync",
1746
+ "mode": "read",
1747
+ "entity": "app_session",
1748
+ "intent": "Find out which app is in the foreground right now \u2014 use this to confirm the app under test is showing and not the home screen or another app.",
1749
+ "guidance": [
1750
+ "Returns the Android package name; it answers with the launcher's package when the app has been sent to the background.",
1751
+ "This is the quickest way to learn the app under test's own package id, which launch_app, terminate_app and uninstall_app all need.",
1752
+ "Android only; get_active_app_info is the iOS equivalent and returns an object rather than a string.",
1753
+ "Called on iOS this is a 405 \"unknown method\" rather than a silent no-op; use 'mobile: activeAppInfo' there."
1754
+ ],
1755
+ "path_params": [
1756
+ {
1757
+ "name": "session_id",
1758
+ "type": "string",
1759
+ "required": true,
1760
+ "example": "0123456789abcdef0123456789abcdef01234567"
1761
+ }
1762
+ ],
1763
+ "body": [
1764
+ {
1765
+ "name": "script",
1766
+ "type": "string",
1767
+ "required": true,
1768
+ "values": [
1769
+ "mobile: getCurrentPackage"
1770
+ ],
1771
+ "example": "mobile: getCurrentPackage"
1772
+ },
1773
+ {
1774
+ "name": "args",
1775
+ "type": "array",
1776
+ "required": true,
1777
+ "description": "Always empty.",
1778
+ "example": []
1779
+ }
1780
+ ],
1781
+ "returns": [
1782
+ "value"
1783
+ ],
1784
+ "responses": {
1785
+ "200": {
1786
+ "description": "The foreground app's package name.",
1787
+ "schema": {
1788
+ "$schema": "StringValueResponse"
1789
+ }
1790
+ },
1791
+ "404": {
1792
+ "$response": "WebDriverError"
1793
+ },
1794
+ "405": {
1795
+ "description": "The command is not implemented on this platform, as \"unknown method\".",
1796
+ "$response": "WebDriverError"
1797
+ }
1798
+ }
1799
+ },
1800
+ {
1801
+ "name": "launch_app",
1802
+ "method": "POST",
1803
+ "path": "/wd/hub/session/{session_id}/execute/sync",
1804
+ "mode": "write",
1805
+ "entity": "app_session",
1806
+ "intent": "Bring an app to the foreground, starting it if it is not running \u2014 use this to open the app under test, return to it, or switch to another installed app.",
1807
+ "guidance": [
1808
+ "Android takes appId and iOS takes bundleId \u2014 send exactly the one for the platform. The wrong name is refused with a 400 \"invalid argument\" that names the parameter it wanted, and no action is taken.",
1809
+ "Launching an app that is already in the foreground does nothing and still reports success.",
1810
+ "To restart an app from a clean state, call terminate_app first and then this.",
1811
+ "Succeeding means the app was activated, not that it finished drawing; read the page source before acting.",
1812
+ "The app must be installed \u2014 a package that is not there fails rather than installing it."
1813
+ ],
1814
+ "path_params": [
1815
+ {
1816
+ "name": "session_id",
1817
+ "type": "string",
1818
+ "required": true,
1819
+ "example": "0123456789abcdef0123456789abcdef01234567"
1820
+ }
1821
+ ],
1822
+ "body": [
1823
+ {
1824
+ "name": "script",
1825
+ "type": "string",
1826
+ "required": true,
1827
+ "values": [
1828
+ "mobile: activateApp"
1829
+ ],
1830
+ "example": "mobile: activateApp"
1831
+ },
1832
+ {
1833
+ "name": "args",
1834
+ "type": "array",
1835
+ "required": true,
1836
+ "description": "Exactly one object naming the app. The key differs by platform.",
1837
+ "fields": [
1838
+ {
1839
+ "name": "appId",
1840
+ "type": "string",
1841
+ "description": "ANDROID ONLY: the package name. iOS rejects this name.",
1842
+ "example": "com.example.app"
1843
+ },
1844
+ {
1845
+ "name": "bundleId",
1846
+ "type": "string",
1847
+ "description": "iOS ONLY: the bundle id. Android rejects this name.",
1848
+ "example": "com.example.App"
1849
+ }
1850
+ ],
1851
+ "example": [
1852
+ {
1853
+ "appId": "com.example.app"
1854
+ }
1855
+ ]
1856
+ }
1857
+ ],
1858
+ "returns": [
1859
+ "value"
1860
+ ],
1861
+ "responses": {
1862
+ "200": {
1863
+ "description": "Null on success.",
1864
+ "schema": {
1865
+ "$schema": "NullValueResponse"
1866
+ }
1867
+ },
1868
+ "404": {
1869
+ "$response": "WebDriverError"
1870
+ }
1871
+ }
1872
+ },
1873
+ {
1874
+ "name": "terminate_app",
1875
+ "method": "POST",
1876
+ "path": "/wd/hub/session/{session_id}/execute/sync",
1877
+ "mode": "write",
1878
+ "entity": "app_session",
1879
+ "intent": "Close an app and stop it running \u2014 use this to end a session inside the app, or as the first half of a restart.",
1880
+ "guidance": [
1881
+ "Android takes appId and iOS takes bundleId \u2014 send exactly the one for the platform. The wrong name is refused with a 400 \"invalid argument\" that names the parameter it wanted, and no action is taken.",
1882
+ "Returns true when the app was running and false when it was not; false is not an error.",
1883
+ "The app stays installed and keeps its data; only the running process is stopped.",
1884
+ "This does not clear state between runs \u2014 relaunching shows the app where it was unless the app itself resets."
1885
+ ],
1886
+ "path_params": [
1887
+ {
1888
+ "name": "session_id",
1889
+ "type": "string",
1890
+ "required": true,
1891
+ "example": "0123456789abcdef0123456789abcdef01234567"
1892
+ }
1893
+ ],
1894
+ "body": [
1895
+ {
1896
+ "name": "script",
1897
+ "type": "string",
1898
+ "required": true,
1899
+ "values": [
1900
+ "mobile: terminateApp"
1901
+ ],
1902
+ "example": "mobile: terminateApp"
1903
+ },
1904
+ {
1905
+ "name": "args",
1906
+ "type": "array",
1907
+ "required": true,
1908
+ "description": "Exactly one object naming the app. The key differs by platform.",
1909
+ "fields": [
1910
+ {
1911
+ "name": "appId",
1912
+ "type": "string",
1913
+ "description": "ANDROID ONLY: the package name. iOS rejects this name.",
1914
+ "example": "com.example.app"
1915
+ },
1916
+ {
1917
+ "name": "bundleId",
1918
+ "type": "string",
1919
+ "description": "iOS ONLY: the bundle id. Android rejects this name.",
1920
+ "example": "com.example.App"
1921
+ }
1922
+ ],
1923
+ "example": [
1924
+ {
1925
+ "appId": "com.example.app"
1926
+ }
1927
+ ]
1928
+ }
1929
+ ],
1930
+ "returns": [
1931
+ "value"
1932
+ ],
1933
+ "responses": {
1934
+ "200": {
1935
+ "description": "True when a running app was stopped.",
1936
+ "schema": {
1937
+ "$schema": "BooleanValueResponse"
1938
+ }
1939
+ },
1940
+ "404": {
1941
+ "$response": "WebDriverError"
1942
+ }
1943
+ }
1944
+ },
1945
+ {
1946
+ "name": "get_app_state",
1947
+ "method": "POST",
1948
+ "path": "/wd/hub/session/{session_id}/execute/sync",
1949
+ "mode": "read",
1950
+ "entity": "app_session",
1951
+ "intent": "Check whether an app is installed, running or in the foreground \u2014 use this to confirm a launch or a close actually took effect.",
1952
+ "guidance": [
1953
+ "Android takes appId and iOS takes bundleId \u2014 send exactly the one for the platform. The wrong name is refused with a 400 \"invalid argument\" that names the parameter it wanted, and no action is taken.",
1954
+ "The value is a number: 0 not installed, 1 installed but not running, 2 running in the background and suspended, 3 running in the background, 4 running in the foreground.",
1955
+ "4 is the only state in which the app is on screen and can be driven.",
1956
+ "The same scale applies on both platforms. Sending an app to the background with press_button home leaves it at 2, and launching it again returns it to 4."
1957
+ ],
1958
+ "path_params": [
1959
+ {
1960
+ "name": "session_id",
1961
+ "type": "string",
1962
+ "required": true,
1963
+ "example": "0123456789abcdef0123456789abcdef01234567"
1964
+ }
1965
+ ],
1966
+ "body": [
1967
+ {
1968
+ "name": "script",
1969
+ "type": "string",
1970
+ "required": true,
1971
+ "values": [
1972
+ "mobile: queryAppState"
1973
+ ],
1974
+ "example": "mobile: queryAppState"
1975
+ },
1976
+ {
1977
+ "name": "args",
1978
+ "type": "array",
1979
+ "required": true,
1980
+ "description": "Exactly one object naming the app. The key differs by platform.",
1981
+ "fields": [
1982
+ {
1983
+ "name": "appId",
1984
+ "type": "string",
1985
+ "description": "ANDROID ONLY: the package name. iOS rejects this name.",
1986
+ "example": "com.example.app"
1987
+ },
1988
+ {
1989
+ "name": "bundleId",
1990
+ "type": "string",
1991
+ "description": "iOS ONLY: the bundle id. Android rejects this name.",
1992
+ "example": "com.example.App"
1993
+ }
1994
+ ],
1995
+ "example": [
1996
+ {
1997
+ "appId": "com.example.app"
1998
+ }
1999
+ ]
2000
+ }
2001
+ ],
2002
+ "returns": [
2003
+ "value"
2004
+ ],
2005
+ "responses": {
2006
+ "200": {
2007
+ "description": "The app's state as a number from 0 to 4.",
2008
+ "schema": {
2009
+ "$schema": "IntegerValueResponse"
2010
+ }
2011
+ },
2012
+ "404": {
2013
+ "$response": "WebDriverError"
2014
+ }
2015
+ }
2016
+ },
2017
+ {
2018
+ "name": "is_app_installed",
2019
+ "method": "POST",
2020
+ "path": "/wd/hub/session/{session_id}/execute/sync",
2021
+ "mode": "read",
2022
+ "entity": "app_session",
2023
+ "intent": "Check whether an app is installed on the device \u2014 use this before launching one, or to confirm an uninstall.",
2024
+ "guidance": [
2025
+ "Android takes appId and iOS takes bundleId \u2014 send exactly the one for the platform. The wrong name is refused with a 400 \"invalid argument\" that names the parameter it wanted, and no action is taken.",
2026
+ "get_app_state answers this and more in one call; prefer it when the running state also matters."
2027
+ ],
2028
+ "path_params": [
2029
+ {
2030
+ "name": "session_id",
2031
+ "type": "string",
2032
+ "required": true,
2033
+ "example": "0123456789abcdef0123456789abcdef01234567"
2034
+ }
2035
+ ],
2036
+ "body": [
2037
+ {
2038
+ "name": "script",
2039
+ "type": "string",
2040
+ "required": true,
2041
+ "values": [
2042
+ "mobile: isAppInstalled"
2043
+ ],
2044
+ "example": "mobile: isAppInstalled"
2045
+ },
2046
+ {
2047
+ "name": "args",
2048
+ "type": "array",
2049
+ "required": true,
2050
+ "description": "Exactly one object naming the app. The key differs by platform.",
2051
+ "fields": [
2052
+ {
2053
+ "name": "appId",
2054
+ "type": "string",
2055
+ "description": "ANDROID ONLY: the package name. iOS rejects this name.",
2056
+ "example": "com.example.app"
2057
+ },
2058
+ {
2059
+ "name": "bundleId",
2060
+ "type": "string",
2061
+ "description": "iOS ONLY: the bundle id. Android rejects this name.",
2062
+ "example": "com.example.App"
2063
+ }
2064
+ ],
2065
+ "example": [
2066
+ {
2067
+ "appId": "com.example.app"
2068
+ }
2069
+ ]
2070
+ }
2071
+ ],
2072
+ "returns": [
2073
+ "value"
2074
+ ],
2075
+ "responses": {
2076
+ "200": {
2077
+ "description": "True when the app is installed.",
2078
+ "schema": {
2079
+ "$schema": "BooleanValueResponse"
2080
+ }
2081
+ },
2082
+ "404": {
2083
+ "$response": "WebDriverError"
2084
+ }
2085
+ },
2086
+ "disabled": true
2087
+ },
2088
+ {
2089
+ "name": "uninstall_app",
2090
+ "method": "POST",
2091
+ "path": "/wd/hub/session/{session_id}/execute/sync",
2092
+ "mode": "write",
2093
+ "entity": "app_session",
2094
+ "intent": "Remove an app and its data from the device for the rest of the session \u2014 use this to test a first-run or upgrade path from a clean state.",
2095
+ "guidance": [
2096
+ "Android takes appId and iOS takes bundleId \u2014 send exactly the one for the platform. The wrong name is refused with a 400 \"invalid argument\" that names the parameter it wanted, and no action is taken.",
2097
+ "The app and everything it stored are gone for the rest of the session and nothing here puts them back; confirm with the user before removing the app under test.",
2098
+ "It is recoverable only in the sense that the device is wiped when the session ends and the next session installs the app fresh.",
2099
+ "Returns true when an app was removed and false when it was not installed.",
2100
+ "Removing the app under test leaves the session on the home screen with nothing to drive."
2101
+ ],
2102
+ "path_params": [
2103
+ {
2104
+ "name": "session_id",
2105
+ "type": "string",
2106
+ "required": true,
2107
+ "example": "0123456789abcdef0123456789abcdef01234567"
2108
+ }
2109
+ ],
2110
+ "body": [
2111
+ {
2112
+ "name": "script",
2113
+ "type": "string",
2114
+ "required": true,
2115
+ "values": [
2116
+ "mobile: removeApp"
2117
+ ],
2118
+ "example": "mobile: removeApp"
2119
+ },
2120
+ {
2121
+ "name": "args",
2122
+ "type": "array",
2123
+ "required": true,
2124
+ "description": "Exactly one object naming the app. The key differs by platform.",
2125
+ "fields": [
2126
+ {
2127
+ "name": "appId",
2128
+ "type": "string",
2129
+ "description": "ANDROID ONLY: the package name. iOS rejects this name.",
2130
+ "example": "com.example.app"
2131
+ },
2132
+ {
2133
+ "name": "bundleId",
2134
+ "type": "string",
2135
+ "description": "iOS ONLY: the bundle id. Android rejects this name.",
2136
+ "example": "com.example.App"
2137
+ }
2138
+ ],
2139
+ "example": [
2140
+ {
2141
+ "appId": "com.example.app"
2142
+ }
2143
+ ]
2144
+ }
2145
+ ],
2146
+ "returns": [
2147
+ "value"
2148
+ ],
2149
+ "responses": {
2150
+ "200": {
2151
+ "description": "True when an app was removed.",
2152
+ "schema": {
2153
+ "$schema": "BooleanValueResponse"
2154
+ }
2155
+ },
2156
+ "404": {
2157
+ "$response": "WebDriverError"
2158
+ }
2159
+ },
2160
+ "disabled": true
2161
+ },
2162
+ {
2163
+ "name": "simulate_biometric",
2164
+ "method": "POST",
2165
+ "path": "/wd/hub/session/{session_id}/execute/sync",
2166
+ "mode": "write",
2167
+ "entity": "app_session",
2168
+ "intent": "Answer a fingerprint, Touch ID or Face ID prompt with a success or a failure \u2014 use this to get past biometric authentication in the app.",
2169
+ "guidance": [
2170
+ "The session must have been started with enable_biometric true; it cannot be turned on once the session is running.",
2171
+ "Call it only while the prompt is actually on screen \u2014 with no prompt showing it fails with BROWSERSTACK_COMMAND_EXECUTION_FAILED.",
2172
+ "biometricMatch takes \"pass\" or \"fail\"; \"fail\" is how to exercise the app's rejection path.",
2173
+ "Read the page source afterwards to see what the app did with the answer.",
2174
+ "Every failure seen so far reports the same BROWSERSTACK_COMMAND_EXECUTION_FAILED regardless of cause, so it does not distinguish 'no prompt on screen' from 'the session was not started with enable_biometric'. Check both."
2175
+ ],
2176
+ "path_params": [
2177
+ {
2178
+ "name": "session_id",
2179
+ "type": "string",
2180
+ "required": true,
2181
+ "example": "0123456789abcdef0123456789abcdef01234567"
2182
+ }
2183
+ ],
2184
+ "body": [
2185
+ {
2186
+ "name": "script",
2187
+ "type": "string",
2188
+ "required": true,
2189
+ "description": "The browserstack_executor line for the biometric action, built as: browserstack_executor: {\"action\":\"biometric\",\"arguments\":{\"biometricMatch\":\"<pass|fail>\"}}",
2190
+ "example": "browserstack_executor: {\"action\":\"biometric\",\"arguments\":{\"biometricMatch\":\"pass\"}}"
2191
+ },
2192
+ {
2193
+ "name": "args",
2194
+ "type": "array",
2195
+ "required": true,
2196
+ "description": "Always an empty array for an executor command.",
2197
+ "example": []
2198
+ }
2199
+ ],
2200
+ "returns": [
2201
+ "value"
2202
+ ],
2203
+ "responses": {
2204
+ "200": {
2205
+ "description": "Acknowledgement that the prompt was answered.",
2206
+ "schema": {
2207
+ "$schema": "AnyValueResponse"
2208
+ }
2209
+ },
2210
+ "404": {
2211
+ "$response": "WebDriverError"
2212
+ }
2213
+ },
2214
+ "disabled": true
2215
+ },
2216
+ {
2217
+ "name": "inject_camera_image",
2218
+ "method": "POST",
2219
+ "path": "/wd/hub/session/{session_id}/execute/sync",
2220
+ "mode": "write",
2221
+ "entity": "app_session",
2222
+ "intent": "Make the app's camera see a chosen image instead of the device camera \u2014 use this to feed a QR code, barcode or document to a scanning flow.",
2223
+ "guidance": [
2224
+ "The session must have been started with enable_camera_image_injection true; it cannot be turned on later.",
2225
+ "imageUrl must be a media:// URL of a file already uploaded to BrowserStack. This surface cannot upload media, because that endpoint takes a file from disk rather than a link \u2014 the URL has to come from elsewhere.",
2226
+ "An unusable URL fails with BROWSERSTACK_INVALID_IMAGE_URL_PASSED rather than silently showing nothing.",
2227
+ "Inject before the app opens its camera; a camera already running keeps the previous source."
2228
+ ],
2229
+ "path_params": [
2230
+ {
2231
+ "name": "session_id",
2232
+ "type": "string",
2233
+ "required": true,
2234
+ "example": "0123456789abcdef0123456789abcdef01234567"
2235
+ }
2236
+ ],
2237
+ "body": [
2238
+ {
2239
+ "name": "script",
2240
+ "type": "string",
2241
+ "required": true,
2242
+ "description": "The browserstack_executor line for the cameraImageInjection action, built as: browserstack_executor: {\"action\":\"cameraImageInjection\",\"arguments\":{\"imageUrl\":\"media://<id>\"}}",
2243
+ "example": "browserstack_executor: {\"action\":\"cameraImageInjection\",\"arguments\":{\"imageUrl\":\"media://0123456789abcdef0123456789abcdef01234567\"}}"
2244
+ },
2245
+ {
2246
+ "name": "args",
2247
+ "type": "array",
2248
+ "required": true,
2249
+ "description": "Always an empty array for an executor command.",
2250
+ "example": []
2251
+ }
2252
+ ],
2253
+ "returns": [
2254
+ "value"
2255
+ ],
2256
+ "responses": {
2257
+ "200": {
2258
+ "description": "Acknowledgement that the image was accepted.",
2259
+ "schema": {
2260
+ "$schema": "AnyValueResponse"
2261
+ }
2262
+ },
2263
+ "404": {
2264
+ "$response": "WebDriverError"
2265
+ }
2266
+ },
2267
+ "disabled": true
2268
+ },
2269
+ {
2270
+ "name": "annotate_session",
2271
+ "method": "POST",
2272
+ "path": "/wd/hub/session/{session_id}/execute/sync",
2273
+ "mode": "write",
2274
+ "entity": "app_session",
2275
+ "intent": "Write a note into the session's timeline so it appears beside the video on the dashboard \u2014 use this to label what each step was doing for whoever reviews the run.",
2276
+ "guidance": [
2277
+ "The note is attached at the moment of the call, so it lines up with that point in the video.",
2278
+ "level takes info, debug, warn or error and changes how the note is shown.",
2279
+ "This records commentary only; it has no effect on the device or on the session's pass/fail status."
2280
+ ],
2281
+ "path_params": [
2282
+ {
2283
+ "name": "session_id",
2284
+ "type": "string",
2285
+ "required": true,
2286
+ "example": "0123456789abcdef0123456789abcdef01234567"
2287
+ }
2288
+ ],
2289
+ "body": [
2290
+ {
2291
+ "name": "script",
2292
+ "type": "string",
2293
+ "required": true,
2294
+ "description": "The browserstack_executor line for the annotate action, built as: browserstack_executor: {\"action\":\"annotate\",\"arguments\":{\"data\":\"<note>\",\"level\":\"<info|debug|warn|error>\"}}",
2295
+ "example": "browserstack_executor: {\"action\":\"annotate\",\"arguments\":{\"data\":\"Tapped Add to cart\",\"level\":\"info\"}}"
2296
+ },
2297
+ {
2298
+ "name": "args",
2299
+ "type": "array",
2300
+ "required": true,
2301
+ "description": "Always an empty array for an executor command.",
2302
+ "example": []
2303
+ }
2304
+ ],
2305
+ "returns": [
2306
+ "value"
2307
+ ],
2308
+ "responses": {
2309
+ "200": {
2310
+ "description": "The note that was recorded.",
2311
+ "schema": {
2312
+ "$schema": "AnyValueResponse"
2313
+ }
2314
+ },
2315
+ "404": {
2316
+ "$response": "WebDriverError"
2317
+ }
2318
+ },
2319
+ "disabled": true
2320
+ },
2321
+ {
2322
+ "name": "is_keyboard_shown",
2323
+ "method": "GET",
2324
+ "path": "/wd/hub/session/{session_id}/appium/device/is_keyboard_shown",
2325
+ "mode": "read",
2326
+ "entity": "app_session",
2327
+ "intent": "Check whether the on-screen keyboard is up \u2014 use this before tapping something low on the screen, or to confirm a field really took focus.",
2328
+ "guidance": [
2329
+ "A keyboard covers the lower part of the screen, so a tap aimed at a control under it lands on the keyboard and still reports success. Check here first when the target sits low.",
2330
+ "True right after typing means the field took focus; false means the tap that was meant to focus it did not land.",
2331
+ "Use hide_keyboard to close it, then check again rather than assuming it closed."
2332
+ ],
2333
+ "path_params": [
2334
+ {
2335
+ "name": "session_id",
2336
+ "type": "string",
2337
+ "required": true,
2338
+ "example": "0123456789abcdef0123456789abcdef01234567"
2339
+ }
2340
+ ],
2341
+ "returns": [
2342
+ "value"
2343
+ ],
2344
+ "responses": {
2345
+ "200": {
2346
+ "description": "True when the keyboard is on screen.",
2347
+ "schema": {
2348
+ "$schema": "BooleanValueResponse"
2349
+ }
2350
+ },
2351
+ "404": {
2352
+ "$response": "WebDriverError"
2353
+ }
2354
+ }
2355
+ },
2356
+ {
2357
+ "name": "press_button",
2358
+ "method": "POST",
2359
+ "path": "/wd/hub/session/{session_id}/execute/sync",
2360
+ "mode": "write",
2361
+ "entity": "app_session",
2362
+ "intent": "Press a hardware button on an iPhone or iPad \u2014 use this for home and the volume keys, which have no on-screen control.",
2363
+ "guidance": [
2364
+ "iOS only; press_keycode is the Android equivalent and the two are not interchangeable.",
2365
+ "The button names carry no separator: home, volumeup, volumedown.",
2366
+ "Pressing home sends the app to the background \u2014 bring it back with launch_app, passing bundleId rather than appId, and confirm from the page source that the app is in front again.",
2367
+ "The response is null on success and says nothing about what the button did."
2368
+ ],
2369
+ "path_params": [
2370
+ {
2371
+ "name": "session_id",
2372
+ "type": "string",
2373
+ "required": true,
2374
+ "example": "0123456789abcdef0123456789abcdef01234567"
2375
+ }
2376
+ ],
2377
+ "body": [
2378
+ {
2379
+ "name": "script",
2380
+ "type": "string",
2381
+ "required": true,
2382
+ "values": [
2383
+ "mobile: pressButton"
2384
+ ],
2385
+ "example": "mobile: pressButton"
2386
+ },
2387
+ {
2388
+ "name": "args",
2389
+ "type": "array",
2390
+ "required": true,
2391
+ "description": "Exactly one object naming the button.",
2392
+ "fields": [
2393
+ {
2394
+ "name": "name",
2395
+ "type": "string",
2396
+ "required": true,
2397
+ "values": [
2398
+ "home",
2399
+ "volumeup",
2400
+ "volumedown"
2401
+ ],
2402
+ "example": "home"
2403
+ }
2404
+ ],
2405
+ "example": [
2406
+ {
2407
+ "name": "home"
2408
+ }
2409
+ ]
2410
+ }
2411
+ ],
2412
+ "returns": [
2413
+ "value"
2414
+ ],
2415
+ "responses": {
2416
+ "200": {
2417
+ "description": "Null on success.",
2418
+ "schema": {
2419
+ "$schema": "NullValueResponse"
2420
+ }
2421
+ },
2422
+ "404": {
2423
+ "$response": "WebDriverError"
2424
+ }
2425
+ }
2426
+ },
2427
+ {
2428
+ "name": "get_simulated_location",
2429
+ "method": "POST",
2430
+ "path": "/wd/hub/session/{session_id}/execute/sync",
2431
+ "mode": "read",
2432
+ "entity": "app_session",
2433
+ "intent": "Read the simulated GPS position on an iPhone or iPad \u2014 use this to confirm a location change took effect.",
2434
+ "guidance": [
2435
+ "iOS only; get_geo_location is the Android equivalent and is refused here.",
2436
+ "Every field comes back null until a position has been set in this session, which means nothing has been simulated rather than that the device is at zero.",
2437
+ "After set_geo_location this returns exactly the latitude, longitude and altitude that were sent, so it is the way to confirm what that call's own response does not.",
2438
+ "altitude reads back as 0 whatever was sent; only latitude and longitude are meaningful."
2439
+ ],
2440
+ "path_params": [
2441
+ {
2442
+ "name": "session_id",
2443
+ "type": "string",
2444
+ "required": true,
2445
+ "example": "0123456789abcdef0123456789abcdef01234567"
2446
+ }
2447
+ ],
2448
+ "body": [
2449
+ {
2450
+ "name": "script",
2451
+ "type": "string",
2452
+ "required": true,
2453
+ "values": [
2454
+ "mobile: getSimulatedLocation"
2455
+ ],
2456
+ "example": "mobile: getSimulatedLocation"
2457
+ },
2458
+ {
2459
+ "name": "args",
2460
+ "type": "array",
2461
+ "required": true,
2462
+ "description": "Always empty.",
2463
+ "example": []
2464
+ }
2465
+ ],
2466
+ "returns": [
2467
+ "value"
2468
+ ],
2469
+ "responses": {
2470
+ "200": {
2471
+ "description": "The simulated position, with null fields when none has been set.",
2472
+ "schema": {
2473
+ "$schema": "LocationResponse"
2474
+ }
2475
+ },
2476
+ "404": {
2477
+ "$response": "WebDriverError"
2478
+ }
2479
+ },
2480
+ "disabled": true
2481
+ },
2482
+ {
2483
+ "name": "get_active_app_info",
2484
+ "method": "POST",
2485
+ "path": "/wd/hub/session/{session_id}/execute/sync",
2486
+ "mode": "read",
2487
+ "entity": "app_session",
2488
+ "intent": "Find out which app is in the foreground on an iPhone or iPad, with its bundle id \u2014 use this to confirm the app under test is showing rather than the home screen.",
2489
+ "guidance": [
2490
+ "iOS only; get_current_app is the Android equivalent and is refused here.",
2491
+ "bundleId is the value the app lifecycle capabilities need on iOS, so this is how to learn it mid-session.",
2492
+ "name is often empty; read bundleId rather than relying on it.",
2493
+ "The answer is an object, where the Android equivalent returns a bare package-name string."
2494
+ ],
2495
+ "path_params": [
2496
+ {
2497
+ "name": "session_id",
2498
+ "type": "string",
2499
+ "required": true,
2500
+ "example": "0123456789abcdef0123456789abcdef01234567"
2501
+ }
2502
+ ],
2503
+ "body": [
2504
+ {
2505
+ "name": "script",
2506
+ "type": "string",
2507
+ "required": true,
2508
+ "values": [
2509
+ "mobile: activeAppInfo"
2510
+ ],
2511
+ "example": "mobile: activeAppInfo"
2512
+ },
2513
+ {
2514
+ "name": "args",
2515
+ "type": "array",
2516
+ "required": true,
2517
+ "description": "Always empty.",
2518
+ "example": []
2519
+ }
2520
+ ],
2521
+ "returns": [
2522
+ "value"
2523
+ ],
2524
+ "responses": {
2525
+ "200": {
2526
+ "description": "The foreground app.",
2527
+ "schema": {
2528
+ "$schema": "ActiveAppInfo"
2529
+ }
2530
+ },
2531
+ "404": {
2532
+ "$response": "WebDriverError"
2533
+ }
2534
+ }
2535
+ },
2536
+ {
2537
+ "name": "read_device_property",
2538
+ "method": "POST",
2539
+ "path": "/wd/hub/session/{session_id}/execute/sync",
2540
+ "mode": "read",
2541
+ "entity": "app_session",
2542
+ "intent": "Read one Android system property from the device, such as its OS build or manufacturer \u2014 use this for details the UI does not show.",
2543
+ "guidance": [
2544
+ "Android only. iOS has no adb and the call is rejected there.",
2545
+ "Only `getprop <property>` can be expressed: the accepted form is fixed, and a command that lists apps or system state cannot be sent through this capability. A public phone is shared between customers, and commands such as `pm list packages` or `dumpsys` report what other people's sessions left behind.",
2546
+ "BrowserStack applies its own allow-list on top of this one and accepts only some properties on a shared phone; ro.build.version.release and ro.product.manufacturer are accepted, while a bare getprop is not. A property it refuses comes back as an execution failure rather than an empty value.",
2547
+ "The device name and OS version are already in the session record, so get_session answers those without this.",
2548
+ "The whole line must be sent exactly as shown, with no extra spaces: it is matched against a fixed form."
2549
+ ],
2550
+ "path_params": [
2551
+ {
2552
+ "name": "session_id",
2553
+ "type": "string",
2554
+ "required": true,
2555
+ "example": "0123456789abcdef0123456789abcdef01234567"
2556
+ }
2557
+ ],
2558
+ "body": [
2559
+ {
2560
+ "name": "script",
2561
+ "type": "string",
2562
+ "required": true,
2563
+ "pattern": "^browserstack_executor: \\{\"action\":\"adbShell\",\"arguments\":\\{\"command\":\"getprop [A-Za-z0-9_.\\-]+\"\\}\\}$",
2564
+ "description": "The executor line carrying a single `getprop <property>` command. Any other command, or any change to the surrounding form, is refused before the request is sent.",
2565
+ "example": "browserstack_executor: {\"action\":\"adbShell\",\"arguments\":{\"command\":\"getprop ro.build.version.release\"}}"
2566
+ },
2567
+ {
2568
+ "name": "args",
2569
+ "type": "array",
2570
+ "required": true,
2571
+ "description": "Always empty.",
2572
+ "example": []
2573
+ }
2574
+ ],
2575
+ "returns": [
2576
+ "value"
2577
+ ],
2578
+ "responses": {
2579
+ "200": {
2580
+ "description": "The property's value as a string.",
2581
+ "schema": {
2582
+ "$schema": "StringValueResponse"
2583
+ }
2584
+ },
2585
+ "404": {
2586
+ "$response": "WebDriverError"
2587
+ }
2588
+ }
2589
+ },
2590
+ {
2591
+ "name": "upload_app_build_from_file",
2592
+ "method": "POST",
2593
+ "path": "/app-automate/upload",
2594
+ "host": "api",
2595
+ "mode": "write",
2596
+ "entity": "app_build",
2597
+ "encoding": "multipart",
2598
+ "local_only": true,
2599
+ "intent": "Upload an app build that is on this machine to App Automate and get back the bs:// URL a session installs \u2014 use this when the .apk, .aab or .ipa is a local file rather than a download link.",
2600
+ "guidance": [
2601
+ "The file must sit inside the directory MCP_UPLOAD_BASE_DIR names. When that is not set the upload is refused and nothing is sent: tell the user to set it to the folder holding their builds and restart the server, then move the build there.",
2602
+ "When it is set but the build is somewhere else, the refusal names the directory the file has to be inside. Tell the user to move it there rather than trying another path.",
2603
+ "Only .apk, .aab and .ipa are accepted, and no part of the path may be a hidden folder, so a build under a dot-directory has to be moved out of it.",
2604
+ "The path is checked before anything is uploaded and before the user is asked to approve, so a refusal costs nothing and means the file was never read.",
2605
+ "upload_app_build takes a public https link instead and is the one to use when the build is not on this machine.",
2606
+ "A large build takes minutes, because the call does not return until the whole file has been sent."
2607
+ ],
2608
+ "body": [
2609
+ {
2610
+ "name": "file",
2611
+ "type": "file",
2612
+ "required": true,
2613
+ "description": "Path to the build on this machine, inside MCP_UPLOAD_BASE_DIR.",
2614
+ "example": "/Users/me/builds/app-release.apk"
2615
+ },
2616
+ {
2617
+ "name": "custom_id",
2618
+ "type": "string",
2619
+ "maxLength": 100,
2620
+ "description": "Stable name for the build, reusable in place of the bs:// URL.",
2621
+ "example": "my-app-staging"
2622
+ }
2623
+ ],
2624
+ "returns": [
2625
+ "app_url",
2626
+ "custom_id",
2627
+ "shareable_id"
2628
+ ],
2629
+ "responses": {
2630
+ "200": {
2631
+ "description": "The uploaded build.",
2632
+ "schema": {
2633
+ "$schema": "AppUpload"
2634
+ }
2635
+ },
2636
+ "401": {
2637
+ "$response": "Unauthorized"
2638
+ }
2639
+ },
2640
+ "unavailable_note": "This server does not run on your own machine, so it cannot read a file you name \u2014 a path sent here would point at the machine hosting the server. Upload the build yourself with a multipart request carrying the file to the same endpoint, which the asset-upload helper will write out for you, and then use the bs:// url it returns as the app for create_session. upload_app_build does the same thing from a public download link, if the build is reachable at one."
2641
+ }
2642
+ ],
2643
+ "schemas": {
2644
+ "NewSessionResponse": {
2645
+ "type": "object",
2646
+ "properties": {
2647
+ "value": {
2648
+ "type": "object",
2649
+ "properties": {
2650
+ "sessionId": {
2651
+ "type": "string",
2652
+ "description": "The session id every other app_session capability takes."
2653
+ },
2654
+ "capabilities": {
2655
+ "type": "object",
2656
+ "description": "Open map of the capabilities the device actually started with; keys are capability names."
2657
+ }
2658
+ }
2659
+ }
2660
+ }
2661
+ },
2662
+ "StringValueResponse": {
2663
+ "type": "object",
2664
+ "properties": {
2665
+ "value": {
2666
+ "type": "string",
2667
+ "description": "A string, or null where the platform returns no value - set_orientation answers null on iOS."
2668
+ }
2669
+ }
2670
+ },
2671
+ "ElementResponse": {
2672
+ "type": "object",
2673
+ "properties": {
2674
+ "value": {
2675
+ "type": "object",
2676
+ "properties": {
2677
+ "element-6066-11e4-a52e-4f735466cecf": {
2678
+ "type": "string",
2679
+ "description": "The element id to pass as element_id."
2680
+ }
2681
+ }
2682
+ }
2683
+ }
2684
+ },
2685
+ "NullValueResponse": {
2686
+ "type": "object",
2687
+ "properties": {
2688
+ "value": {
2689
+ "type": "null"
2690
+ }
2691
+ }
2692
+ },
2693
+ "AnyValueResponse": {
2694
+ "type": "object",
2695
+ "properties": {
2696
+ "value": {
2697
+ "description": "Command-specific result: null, a boolean, a string or an object."
2698
+ }
2699
+ }
2700
+ },
2701
+ "RectResponse": {
2702
+ "type": "object",
2703
+ "properties": {
2704
+ "value": {
2705
+ "type": "object",
2706
+ "properties": {
2707
+ "x": {
2708
+ "type": "integer"
2709
+ },
2710
+ "y": {
2711
+ "type": "integer"
2712
+ },
2713
+ "width": {
2714
+ "type": "integer"
2715
+ },
2716
+ "height": {
2717
+ "type": "integer"
2718
+ }
2719
+ }
2720
+ }
2721
+ }
2722
+ },
2723
+ "WebDriverErrorBody": {
2724
+ "type": "object",
2725
+ "properties": {
2726
+ "value": {
2727
+ "type": "object",
2728
+ "properties": {
2729
+ "error": {
2730
+ "type": "string",
2731
+ "description": "W3C error code such as \"no such element\" or \"invalid session id\"."
2732
+ },
2733
+ "message": {
2734
+ "type": "string"
2735
+ }
2736
+ }
2737
+ }
2738
+ }
2739
+ },
2740
+ "BooleanValueResponse": {
2741
+ "type": "object",
2742
+ "properties": {
2743
+ "value": {
2744
+ "type": "boolean"
2745
+ }
2746
+ }
2747
+ },
2748
+ "StringListResponse": {
2749
+ "type": "object",
2750
+ "properties": {
2751
+ "value": {
2752
+ "type": "array",
2753
+ "items": {
2754
+ "type": "string"
2755
+ }
2756
+ }
2757
+ }
2758
+ },
2759
+ "LocationResponse": {
2760
+ "type": "object",
2761
+ "properties": {
2762
+ "value": {
2763
+ "type": "object",
2764
+ "properties": {
2765
+ "latitude": {
2766
+ "type": "number"
2767
+ },
2768
+ "longitude": {
2769
+ "type": "number"
2770
+ },
2771
+ "altitude": {
2772
+ "type": "number",
2773
+ "description": "Often a negligible non-zero value such as 5e-324 rather than exactly 0."
2774
+ }
2775
+ }
2776
+ }
2777
+ }
2778
+ },
2779
+ "DeviceList": {
2780
+ "type": "array",
2781
+ "items": {
2782
+ "type": "object",
2783
+ "properties": {
2784
+ "device": {
2785
+ "type": "string",
2786
+ "description": "Device name, to be passed to create_session exactly as given."
2787
+ },
2788
+ "os": {
2789
+ "type": "string",
2790
+ "description": "android or ios."
2791
+ },
2792
+ "os_version": {
2793
+ "type": "string"
2794
+ },
2795
+ "device_tier": {
2796
+ "type": "string",
2797
+ "description": "Tier 1 to Tier 4; tiers group devices by how widely used they are, not by capability."
2798
+ },
2799
+ "device_limit": {
2800
+ "type": "integer",
2801
+ "description": "How many sessions the account may run on this model at once."
2802
+ },
2803
+ "realMobile": {
2804
+ "type": "boolean",
2805
+ "description": "True for a physical device."
2806
+ }
2807
+ }
2808
+ }
2809
+ },
2810
+ "DedicatedDeviceList": {
2811
+ "type": "array",
2812
+ "description": "Private devices, returned as the product sends them; rows carry more fields than the public catalogue.",
2813
+ "items": {
2814
+ "type": "object",
2815
+ "properties": {
2816
+ "device": {
2817
+ "type": "string"
2818
+ },
2819
+ "os": {
2820
+ "type": "string"
2821
+ },
2822
+ "os_version": {
2823
+ "type": "string"
2824
+ }
2825
+ }
2826
+ }
2827
+ },
2828
+ "Plan": {
2829
+ "type": "object",
2830
+ "properties": {
2831
+ "automate_plan": {
2832
+ "type": "string"
2833
+ },
2834
+ "parallel_sessions_running": {
2835
+ "type": "integer",
2836
+ "description": "Account-wide, including sessions other people and CI started."
2837
+ },
2838
+ "parallel_sessions_max_allowed": {
2839
+ "type": "integer"
2840
+ },
2841
+ "team_parallel_sessions_max_allowed": {
2842
+ "type": "integer",
2843
+ "description": "Ceiling shared by the whole team; can be lower than the account maximum."
2844
+ },
2845
+ "queued_sessions": {
2846
+ "type": "integer"
2847
+ },
2848
+ "queued_sessions_max_allowed": {
2849
+ "type": "integer"
2850
+ },
2851
+ "terminal_access": {
2852
+ "type": "string"
2853
+ }
2854
+ }
2855
+ },
2856
+ "AppUpload": {
2857
+ "type": "object",
2858
+ "properties": {
2859
+ "app_url": {
2860
+ "type": "string",
2861
+ "description": "The bs:// URL create_session installs."
2862
+ },
2863
+ "custom_id": {
2864
+ "type": "string"
2865
+ },
2866
+ "shareable_id": {
2867
+ "type": "string",
2868
+ "description": "username/custom_id form, usable by teammates."
2869
+ }
2870
+ }
2871
+ },
2872
+ "AppBuildList": {
2873
+ "type": "array",
2874
+ "items": {
2875
+ "type": "object",
2876
+ "properties": {
2877
+ "app_name": {
2878
+ "type": "string"
2879
+ },
2880
+ "app_version": {
2881
+ "type": "string"
2882
+ },
2883
+ "app_url": {
2884
+ "type": "string",
2885
+ "description": "The bs:// URL create_session takes."
2886
+ },
2887
+ "app_id": {
2888
+ "type": "string",
2889
+ "description": "Internal identifier; not accepted by create_session."
2890
+ },
2891
+ "custom_id": {
2892
+ "type": "string"
2893
+ },
2894
+ "shareable_id": {
2895
+ "type": "string"
2896
+ },
2897
+ "uploaded_at": {
2898
+ "type": "string"
2899
+ }
2900
+ }
2901
+ }
2902
+ },
2903
+ "AppDetails": {
2904
+ "type": "object",
2905
+ "properties": {
2906
+ "app_name": {
2907
+ "type": "string"
2908
+ },
2909
+ "app_version": {
2910
+ "type": "string"
2911
+ },
2912
+ "app_url": {
2913
+ "type": "string"
2914
+ },
2915
+ "app_custom_id": {
2916
+ "type": "string"
2917
+ },
2918
+ "app_filename": {
2919
+ "type": "string"
2920
+ },
2921
+ "uploaded_at": {
2922
+ "type": "string"
2923
+ }
2924
+ }
2925
+ },
2926
+ "SessionStatusUpdate": {
2927
+ "type": "object",
2928
+ "properties": {
2929
+ "automation_session": {
2930
+ "type": "object",
2931
+ "properties": {
2932
+ "hashed_id": {
2933
+ "type": "string",
2934
+ "description": "The session id."
2935
+ },
2936
+ "name": {
2937
+ "type": "string"
2938
+ },
2939
+ "status": {
2940
+ "type": "string",
2941
+ "description": "passed, failed, or absent when never set."
2942
+ },
2943
+ "reason": {
2944
+ "type": "string"
2945
+ },
2946
+ "device": {
2947
+ "type": "string"
2948
+ },
2949
+ "os": {
2950
+ "type": "string"
2951
+ },
2952
+ "os_version": {
2953
+ "type": "string"
2954
+ },
2955
+ "build_name": {
2956
+ "type": "string"
2957
+ },
2958
+ "project_name": {
2959
+ "type": "string"
2960
+ }
2961
+ }
2962
+ }
2963
+ },
2964
+ "description": "The session record after the change, wrapped in automation_session."
2965
+ },
2966
+ "SessionDetails": {
2967
+ "type": "object",
2968
+ "properties": {
2969
+ "automation_session": {
2970
+ "type": "object",
2971
+ "properties": {
2972
+ "hashed_id": {
2973
+ "type": "string",
2974
+ "description": "The session id."
2975
+ },
2976
+ "name": {
2977
+ "type": "string"
2978
+ },
2979
+ "status": {
2980
+ "type": "string",
2981
+ "description": "passed, failed, or absent when never set."
2982
+ },
2983
+ "reason": {
2984
+ "type": "string"
2985
+ },
2986
+ "device": {
2987
+ "type": "string"
2988
+ },
2989
+ "os": {
2990
+ "type": "string"
2991
+ },
2992
+ "os_version": {
2993
+ "type": "string"
2994
+ },
2995
+ "build_name": {
2996
+ "type": "string"
2997
+ },
2998
+ "project_name": {
2999
+ "type": "string"
3000
+ },
3001
+ "duration": {
3002
+ "type": "integer",
3003
+ "description": "Seconds; null while the session is still running."
3004
+ },
3005
+ "created_at": {
3006
+ "type": "string"
3007
+ },
3008
+ "public_url": {
3009
+ "type": "string",
3010
+ "description": "Shareable link that needs no login; it carries an auth token, so treat it as a secret."
3011
+ },
3012
+ "video_url": {
3013
+ "type": "string",
3014
+ "description": "Complete only once the session has ended."
3015
+ },
3016
+ "browserstack_status": {
3017
+ "type": "string",
3018
+ "description": "What BrowserStack observed, which can differ from the status that was set."
3019
+ },
3020
+ "app_details": {
3021
+ "$schema": "AppDetails"
3022
+ },
3023
+ "browser_url": {
3024
+ "type": "string",
3025
+ "description": "The session on the dashboard; needs a BrowserStack login."
3026
+ }
3027
+ }
3028
+ }
3029
+ },
3030
+ "description": "The session record, wrapped in automation_session."
3031
+ },
3032
+ "MessageResponse": {
3033
+ "type": "object",
3034
+ "properties": {
3035
+ "message": {
3036
+ "type": "string"
3037
+ }
3038
+ }
3039
+ },
3040
+ "IntegerValueResponse": {
3041
+ "type": "object",
3042
+ "properties": {
3043
+ "value": {
3044
+ "type": "integer"
3045
+ }
3046
+ }
3047
+ },
3048
+ "ActiveAppInfo": {
3049
+ "type": "object",
3050
+ "properties": {
3051
+ "value": {
3052
+ "type": "object",
3053
+ "properties": {
3054
+ "bundleId": {
3055
+ "type": "string"
3056
+ },
3057
+ "pid": {
3058
+ "type": "integer",
3059
+ "description": "Process id; a web view context name is built from it."
3060
+ },
3061
+ "name": {
3062
+ "type": "string",
3063
+ "description": "Often empty."
3064
+ }
3065
+ }
3066
+ }
3067
+ }
3068
+ }
3069
+ },
3070
+ "responses": {
3071
+ "WebDriverError": {
3072
+ "description": "W3C WebDriver error; read value.error for the code.",
3073
+ "schema": {
3074
+ "$schema": "WebDriverErrorBody"
3075
+ }
3076
+ },
3077
+ "Unauthorized": {
3078
+ "description": "The BrowserStack username or access key was rejected."
3079
+ }
3080
+ },
3081
+ "paging": {},
3082
+ "entities": {
3083
+ "app_session": {
3084
+ "title": "App Session",
3085
+ "description": "A live Appium session on one real device running one installed app, driven command by command until it ends.",
3086
+ "aliases": [
3087
+ "appium session",
3088
+ "app automate session",
3089
+ "device session",
3090
+ "live session",
3091
+ "webdriver session",
3092
+ "mobile session",
3093
+ "test session"
3094
+ ],
3095
+ "id_convention": "session_id is a 40-character hex string returned by create_session at value.sessionId; it cannot be listed or guessed. element_id is an opaque string from find_element, valid only until the screen changes.",
3096
+ "key_facts": [
3097
+ "A session holds a real device and consumes App Automate minutes from start until it ends; it ends itself after the idle timeout with no command, or after 2 hours.",
3098
+ "The app must already be uploaded before a session can start; a build is uploaded by giving a public download link, not by sending a file.",
3099
+ "Actions are decided from the UI tree: read the page source, act, then read it again to confirm the effect.",
3100
+ "Each session is recorded with video and logs on the App Automate dashboard, including its passed or failed status.",
3101
+ "A session's record outlives the session: its status, shareable link and video stay available on the account after the device is released.",
3102
+ "Apps can be launched, closed and removed during a session, and anything removed comes back only when a later session installs it again."
3103
+ ],
3104
+ "capabilities": [
3105
+ "create_session",
3106
+ "get_page_source",
3107
+ "find_element",
3108
+ "click_element",
3109
+ "send_keys",
3110
+ "perform_actions",
3111
+ "execute_script",
3112
+ "get_window_rect",
3113
+ "delete_session",
3114
+ "take_screenshot",
3115
+ "press_back",
3116
+ "get_orientation",
3117
+ "set_orientation",
3118
+ "list_contexts",
3119
+ "set_context",
3120
+ "hide_keyboard",
3121
+ "set_geo_location",
3122
+ "get_geo_location",
3123
+ "push_file",
3124
+ "pull_file",
3125
+ "press_keycode",
3126
+ "get_session",
3127
+ "set_session_status",
3128
+ "set_session_network",
3129
+ "get_current_app",
3130
+ "launch_app",
3131
+ "terminate_app",
3132
+ "get_app_state",
3133
+ "is_app_installed",
3134
+ "uninstall_app",
3135
+ "simulate_biometric",
3136
+ "inject_camera_image",
3137
+ "annotate_session",
3138
+ "is_keyboard_shown",
3139
+ "press_button",
3140
+ "get_simulated_location",
3141
+ "get_active_app_info",
3142
+ "read_device_property"
3143
+ ]
3144
+ },
3145
+ "device": {
3146
+ "title": "Device",
3147
+ "description": "A real phone or tablet model in the BrowserStack cloud, identified by its name and OS version, that a session can run on.",
3148
+ "aliases": [
3149
+ "device",
3150
+ "real device",
3151
+ "phone",
3152
+ "handset",
3153
+ "mobile device",
3154
+ "android device",
3155
+ "ios device",
3156
+ "iphone",
3157
+ "tablet",
3158
+ "private device",
3159
+ "dedicated device",
3160
+ "device cloud"
3161
+ ],
3162
+ "id_convention": "A device is addressed by its name and OS version as two strings, exactly as the device list spells them; there is no numeric id and no abbreviation is accepted.",
3163
+ "key_facts": [
3164
+ "Public devices are shared with other customers and are taken for the length of a session, so one can be busy even though the account is entitled to it.",
3165
+ "Private (dedicated) devices are reserved for the team and are the only ones a device tunnel can be opened on.",
3166
+ "A device's tier groups it by how widely used the model is, not by what it can do.",
3167
+ "The catalogue changes as models are added and retired, so a device name held from an earlier run can stop being offered."
3168
+ ],
3169
+ "capabilities": [
3170
+ "list_devices",
3171
+ "list_dedicated_devices"
3172
+ ]
3173
+ },
3174
+ "app_build": {
3175
+ "title": "App Build",
3176
+ "description": "An uploaded app binary stored on BrowserStack, addressed by a bs:// URL, that a session installs on the device.",
3177
+ "aliases": [
3178
+ "app",
3179
+ "app build",
3180
+ "build",
3181
+ "binary",
3182
+ "apk",
3183
+ "aab",
3184
+ "ipa",
3185
+ "app upload",
3186
+ "uploaded app",
3187
+ "app url"
3188
+ ],
3189
+ "id_convention": "app_url is a bs:// URL of 40 hex characters. A build may also carry a custom_id, a name chosen at upload that always resolves to the newest build under it.",
3190
+ "key_facts": [
3191
+ "A build must be uploaded before any session can install it, either from a public download link or from a file on the machine running this server.",
3192
+ "Builds expire 30 days after upload, so a bs:// URL kept from an earlier run can stop working.",
3193
+ "Uploading under a custom id that already exists adds another build rather than replacing the old one, and both stay listed.",
3194
+ "A build uploaded by one person is visible to the whole team."
3195
+ ],
3196
+ "capabilities": [
3197
+ "upload_app_build",
3198
+ "list_app_builds",
3199
+ "list_team_app_builds",
3200
+ "get_app_builds_by_custom_id",
3201
+ "upload_app_build_from_file"
3202
+ ]
3203
+ },
3204
+ "plan": {
3205
+ "title": "Plan",
3206
+ "description": "The account's App Automate subscription and how much of its parallel-session allowance is in use.",
3207
+ "aliases": [
3208
+ "plan",
3209
+ "subscription",
3210
+ "parallel sessions",
3211
+ "parallels",
3212
+ "concurrency",
3213
+ "quota",
3214
+ "capacity",
3215
+ "limits",
3216
+ "usage"
3217
+ ],
3218
+ "id_convention": "There is one plan per account; it is not addressed by an id.",
3219
+ "key_facts": [
3220
+ "Parallel-session counts are account-wide and include sessions started by other people and by CI, not only the caller's.",
3221
+ "A team can have a lower ceiling than the account as a whole.",
3222
+ "Starting a session past the limit queues it rather than refusing it, which looks like a slow start."
3223
+ ],
3224
+ "capabilities": [
3225
+ "get_plan"
3226
+ ]
3227
+ }
3228
+ },
3229
+ "hosts": {
3230
+ "api": "https://api-cloud.browserstack.com"
3231
+ }
3232
+ }
3233
+ }