@browserstack/mcp-server 2.3.0-beta.6 → 2.3.0-beta.7

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.
@@ -3,7 +3,7 @@
3
3
  "version": "1.0",
4
4
  "build_id": "6782f7e_2026-10-16T13:54:22Z",
5
5
  "mobiledevicecontrol": {
6
- "summary": "Mobile device control: 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, move between the native app and a web view, and launch or close apps. Apps are RUN here \u2014 unlike Test Management, which authors cases, and Test Reporting, which records results pushed in from a runner. Browsers are not here \u2014 those are device control, which drives a page rather than an installed app \u2014 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.",
6
+ "summary": "Mobile device control: 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: find elements and read their text, position and attributes, tap them, type, swipe, screenshot, press keys, rotate, switch between the native app and a web view, move between the native app and a web view, and launch or close apps. Apps are RUN here \u2014 unlike Test Management, which authors cases, and Test Reporting, which records results pushed in from a runner. Browsers are not here \u2014 those are device control, which drives a page rather than an installed app \u2014 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
7
  "routing_terms": [
8
8
  "app automate",
9
9
  "appium",
@@ -74,7 +74,7 @@
74
74
  "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.",
75
75
  "The session ends itself after idle_timeout seconds with no command (90 by default, 900 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.",
76
76
  "Every session occupies a device and consumes App Automate minutes until it ends: always finish with set_session_status and then delete_session.",
77
- "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.",
77
+ "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 find the element you are about to act on again before every action.",
78
78
  "device_name and platform_version must be a combination mobile device control offers; an unknown pair fails with a session-not-created error naming the capability.",
79
79
  "Set appium_version to 2.19.0 for any session that will scroll with scroll_gesture, dismiss an iOS keyboard with dismiss_keyboard_key, or use any other mobile: capability; the default server is 1.x and answers those with a 404 listing the commands it does know.",
80
80
  "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.",
@@ -83,7 +83,7 @@
83
83
  "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.",
84
84
  "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.",
85
85
  "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.",
86
- "LANGUAGE AND LOCALE LAND IN DIFFERENT PLACES ON THE TWO PLATFORMS. On Android they change the device, and the app with it \u2014 measured with language de and locale DE: the system's own permission dialog came up in German and the app rendered German throughout. On iOS they leave the device alone \u2014 Settings and General stay in English \u2014 and apply to the app under test at launch. Either way nothing is visible unless the app actually ships that language, so test with an app that does and read the strings back from get_page_source rather than trusting the call's 200.",
86
+ "LANGUAGE AND LOCALE LAND IN DIFFERENT PLACES ON THE TWO PLATFORMS. On Android they change the device, and the app with it \u2014 measured with language de and locale DE: the system's own permission dialog came up in German and the app rendered German throughout. On iOS they leave the device alone \u2014 Settings and General stay in English \u2014 and apply to the app under test at launch. Either way nothing is visible unless the app actually ships that language, so test with an app that does and read the strings back with get_element_text rather than trusting the call's 200.",
87
87
  "Both are accepted without complaint on both platforms, so a 200 says nothing about whether anything changed. There is also no way to confirm the device locale from inside the session: read_device_property refuses getprop persist.sys.locale and ro.product.locale as unsupported commands. Judge it from what is on screen.",
88
88
  "THIS CALL RETURNS NO LINK \u2014 only value.sessionId. Nothing here can open a browser for the user either: this surface makes HTTP requests, and on a remote server there is no browser to open. So never say a live view was opened. If the user may want to watch, call get_session straight after this and give them browser_url in the same reply, which is the session on the dashboard and needs a BrowserStack login. public_url needs no login but carries an auth token, so offer it only when they ask to share outside the team.",
89
89
  "Someone can only WATCH AND INTERACT if interactive_debugging was set true in this call \u2014 it cannot be turned on once the session is running, it is off by default on Android, and it needs video left on. Ask for it up front when a person is going to follow along, because the alternative is restarting the session."
@@ -321,11 +321,7 @@
321
321
  "entity": "app_session",
322
322
  "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.",
323
323
  "guidance": [
324
- "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.",
325
- "Read it again after every action: the tree is a snapshot, and element ids from find_element go stale once the screen changes.",
326
- "Android bounds are \"[x1,y1][x2,y2]\" in device pixels \u2014 the centre of that box is the coordinate to use with perform_actions.",
327
- "A product-specific attribute is the best locator: resource-id for find_element using \"id\", content-desc for \"accessibility id\".",
328
- "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."
324
+ "Not published: the whole tree runs to tens of kilobytes, more than a caller can read. Find the elements you need with find_element or find_elements and read them with get_element_text, get_element_rect and get_element_attribute."
329
325
  ],
330
326
  "path_params": [
331
327
  {
@@ -349,7 +345,8 @@
349
345
  "404": {
350
346
  "$response": "WebDriverError"
351
347
  }
352
- }
348
+ },
349
+ "disabled": true
353
350
  },
354
351
  {
355
352
  "name": "find_element",
@@ -360,8 +357,8 @@
360
357
  "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.",
361
358
  "guidance": [
362
359
  "The element id comes back under the W3C key value[\"element-6066-11e4-a52e-4f735466cecf\"]; pass that string as element_id.",
363
- "Take the selector from get_page_source rather than guessing: resource-id \u2192 \"id\", content-desc (Android) or name (iOS) \u2192 \"accessibility id\".",
364
- "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.",
360
+ "Locators by platform: resource-id \u2192 \"id\", content-desc (Android) or name (iOS) \u2192 \"accessibility id\", visible text \u2192 xpath such as //*[@text='Submit'] on Android or -ios predicate string label == 'Submit' on iOS. Once you hold an element, get_element_attribute reads its ids and get_element_text what it says.",
361
+ "No match is a 404 with error \"no such element\" \u2014 the screen may have changed, or the element may be off-screen and need a scroll first; find it again after the scroll. \"No such element\" is also the cheapest way to confirm something is NOT on screen.",
365
362
  "\"-android uiautomator\" accepts a UiSelector expression, which can scroll an element into view: new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text(\"Checkout\")).",
366
363
  "find_elements answers the same query with a list and never a 404, which is how to ask whether something is present without treating absence as a failure."
367
364
  ],
@@ -421,8 +418,8 @@
421
418
  "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.",
422
419
  "guidance": [
423
420
  "element_id must come from find_element on the current screen; after the screen changes it is stale and fails with \"stale element reference\".",
424
- "To tap something that has no usable locator, tap its bounds centre with perform_actions instead.",
425
- "Read get_page_source afterwards to confirm the tap had the intended effect \u2014 a 200 only means the tap was delivered.",
421
+ "To tap something that has no usable locator, take the centre of a nearby element's get_element_rect and tap the coordinates with perform_actions instead.",
422
+ "Confirm the tap had the intended effect \u2014 a 200 only means it was delivered: find an element the new screen should carry, or read a label with get_element_text before and after.",
426
423
  "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."
427
424
  ],
428
425
  "path_params": [
@@ -463,10 +460,10 @@
463
460
  "entity": "app_session",
464
461
  "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.",
465
462
  "guidance": [
466
- "WHETHER THIS REPLACES OR APPENDS DEPENDS ON THE PLATFORM: on Android it replaces whatever the field already holds, and on iOS it appends to it. Typing twice into one iOS field therefore leaves both values joined. Read the field back from get_page_source whenever the resulting value matters.",
463
+ "WHETHER THIS REPLACES OR APPENDS DEPENDS ON THE PLATFORM: on Android it replaces whatever the field already holds, and on iOS it appends to it. Typing twice into one iOS field therefore leaves both values joined. Read the field back with get_element_text whenever the resulting value matters.",
467
464
  "The element must be an editable field (EditText on Android, TextField on iOS); typing into anything else fails with \"invalid element state\".",
468
- "The on-screen keyboard may stay open afterwards and cover lower elements \u2014 re-read the page source before the next tap.",
469
- "clear_element empties a field, and on iOS \u2014 where this APPENDS \u2014 it is the only way to take back what is already there. It answers 200 whether or not the field changed, so read the field back from get_page_source rather than trusting the status.",
465
+ "The on-screen keyboard may stay open afterwards and cover lower elements \u2014 check is_keyboard_shown, and compare get_element_rect of the next target with the screen's lower part, before the next tap.",
466
+ "clear_element empties a field, and on iOS \u2014 where this APPENDS \u2014 it is the only way to take back what is already there. It answers 200 whether or not the field changed, so read the field back with get_element_text rather than trusting the status.",
470
467
  "The keyboard is usually left open afterwards. On Android and on an iPad, hide_keyboard closes it. On an iPhone it cannot be closed without submitting, so read hide_keyboard's guidance before planning a tap below the field.",
471
468
  "Sending a newline closes the keyboard on iOS by pressing Return, which an app that commits on editing-ended will treat as a submit \u2014 the field resets and the value moves wherever the app sends it. The commit is the app's doing, not the newline's: dismissing the same field any other way can commit it too. See hide_keyboard."
472
469
  ],
@@ -517,7 +514,7 @@
517
514
  "intent": "Empty an editable field in the app \u2014 use this before typing when the field already holds something and you need to replace rather than add to it.",
518
515
  "guidance": [
519
516
  "Takes no parameters of its own.",
520
- "CONFIRM IT ACTUALLY EMPTIED THE FIELD by reading the field back from get_page_source: this endpoint can answer 200 without the field changing, and the status alone does not tell you which happened.",
517
+ "CONFIRM IT ACTUALLY EMPTIED THE FIELD by reading it back with get_element_text: this endpoint can answer 200 without the field changing, and the status alone does not tell you which happened.",
521
518
  "Needed far more on iOS than on Android: send_keys REPLACES the field's contents on Android, so clearing first is usually unnecessary there, while on iOS it APPENDS and this is the only way to take back what is already in the field.",
522
519
  "The element must be an editable field \u2014 EditText on Android, TextField on iOS. Anything else answers \"invalid element state\".",
523
520
  "The element_id must come from find_element on the current screen; after the screen changes it is stale and fails with \"stale element reference\"."
@@ -561,9 +558,9 @@
561
558
  "entity": "app_session",
562
559
  "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.",
563
560
  "guidance": [
564
- "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.",
561
+ "Coordinates are device pixels on Android and points on iOS \u2014 the space get_element_rect and get_window_rect report in; take them from there rather than from a screenshot, whose height can include the system bars.",
565
562
  "Tap: one touch pointer with pointerMove(duration 0, x, y), pointerDown(button 0), pause(100), pointerUp(button 0).",
566
- "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.",
563
+ "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 get_element_rect. For a plain scroll or swipe, scroll_gesture (Android) and scroll_view (iOS) need no coordinates at all.",
567
564
  "Long-press: replace the pause duration with 1000 or more.",
568
565
  "For a scroll without computing coordinates, scroll_gesture is simpler on Android and reports whether more content remains; on iOS use scroll_view; compute a swipe here only when neither fits.",
569
566
  "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."
@@ -662,10 +659,11 @@
662
659
  "path": "/wd/hub/session/{session_id}/execute/sync",
663
660
  "mode": "write",
664
661
  "entity": "app_session",
665
- "intent": "Scroll a region of the screen in a direction \u2014 use this to move through a list or page without working out start and end coordinates yourself.",
662
+ "intent": "Swipe or scroll a region of the screen in a direction \u2014 use this to move through a list or page, or to swipe left and right through a carousel of cards, without working out start and end coordinates yourself.",
666
663
  "guidance": [
667
664
  "ANDROID ONLY. On iOS this is a 405 \"unknown method\"; scroll_view is the iOS counterpart.",
668
- "The returned value is true when the view can still scroll further in that direction and false when it cannot \u2014 but the FIRST scroll of a freshly opened list can answer false while the screen moved and more content followed, so treat false as a hint and confirm from the page source before deciding you are at the end.",
665
+ "The returned value is true when the view can still scroll further in that direction and false when it cannot \u2014 but the FIRST scroll of a freshly opened list can answer false while the screen moved and more content followed, so treat false as a hint and confirm by finding the element you expect before deciding you are at the end.",
666
+ "A HORIZONTAL SWIPE IS THE SAME CALL with direction left or right: a rectangle covering the carousel's band of the screen, taken from get_window_rect, is enough \u2014 no element bounds are needed, and perform_actions is the harder way to do the same thing. DIRECTION NAMES WHERE THE CONTENT COMES FROM, as with a list: down brings what is below into view (the finger moves up), so right brings the NEXT card and left the previous one.",
669
667
  "The area is a rectangle in screen pixels \u2014 get_window_rect gives the screen size to base it on \u2014 and percent is how far to scroll within it, 0 to 1.",
670
668
  "Needs Appium 2.x: set appium_version to 2.19.0 at session start, since the default 1.x server answers mobile: commands with a 404 listing what it does know."
671
669
  ],
@@ -691,7 +689,7 @@
691
689
  "name": "args",
692
690
  "type": "array",
693
691
  "required": true,
694
- "description": "Exactly one object: the rectangle to scroll inside, the direction, and how far.",
692
+ "description": "Exactly one object: the rectangle to scroll inside, the direction (up, down, left or right), and how far.",
695
693
  "example": [
696
694
  {
697
695
  "left": 100,
@@ -728,13 +726,14 @@
728
726
  "path": "/wd/hub/session/{session_id}/execute/sync",
729
727
  "mode": "write",
730
728
  "entity": "app_session",
731
- "intent": "Scroll a scrollable view on iOS in a direction, or until a named element is visible \u2014 use this to move through a list or a long screen.",
729
+ "intent": "Swipe or scroll a scrollable view on iOS in a direction, or until a named element is visible \u2014 use this to move through a list or a long screen, or to swipe left and right through a carousel.",
732
730
  "guidance": [
733
731
  "iOS ONLY, and the counterpart of scroll_gesture. On Android the same command exists with a different shape and answers 400 \"invalid argument\" naming strategy and selector as missing; use scroll_gesture there.",
734
732
  "Scroll by direction with {\"direction\":\"down\"}, or stop when something comes into view with {\"predicateString\":\"name == 'Submit'\", \"toVisible\": true} \u2014 the second is the reliable way to reach a known control without guessing how far to go.",
733
+ "A HORIZONTAL SWIPE IS THE SAME CALL with direction left or right, optionally with elementId naming the carousel; no coordinates are needed.",
735
734
  "Pass elementId in args to scroll inside that view rather than the whole screen; without one it scrolls the first scrollable view it finds, which may not be the one you meant.",
736
735
  "Needs Appium 2.x: set appium_version to 2.19.0 at session start, since the default 1.x server answers mobile: commands with a 404 listing what it does know.",
737
- "Confirm from get_page_source that the screen actually moved \u2014 this answers 200 even on a screen with nothing scrollable, so a 200 only says the gesture was delivered."
736
+ "Confirm the screen actually moved \u2014 find the element you were scrolling toward, or compare get_element_rect of one element before and after \u2014 since this answers 200 even on a screen with nothing scrollable: a 200 only says the gesture was delivered."
738
737
  ],
739
738
  "path_params": [
740
739
  {
@@ -793,7 +792,7 @@
793
792
  "intent": "Close the on-screen keyboard by pressing one of its own keys \u2014 use this on an iPhone, where the plain hide_keyboard is refused.",
794
793
  "guidance": [
795
794
  "hide_keyboard already presses these keys on an iPhone when its plain call is refused \u2014 Done-type keys first, then Return \u2014 and reports each attempt. Call this directly to press exactly the key you choose: Return alone, or every key but Return when a submit must not happen.",
796
- "The key must EXIST on that keyboard or this returns the same 400: read the keyboard from get_page_source and pick from the keys that are really there. An iPad carries \"Hide keyboard\"; an iPhone keyboard of this kind usually offers only Return.",
795
+ "The key must EXIST on that keyboard or this returns the same 400: list the keyboard's keys with find_elements using class name XCUIElementTypeKey and read each with get_element_text, then pick from the keys that are really there. An iPad carries \"Hide keyboard\"; an iPhone keyboard of this kind usually offers only Return.",
797
796
  "Several candidates may be listed in one call and it costs nothing, but it still fails when the keyboard carries none of them \u2014 measured with [\"Done\",\"done\",\"Hide keyboard\",\"Dismiss\"].",
798
797
  "PRESSING RETURN SUBMITS. An app that commits when the field stops being edited will commit however the keyboard is closed, so check the field afterwards rather than assuming only the keyboard moved.",
799
798
  "Confirm with is_keyboard_shown; on Android and on an iPad hide_keyboard closes it on its own."
@@ -925,7 +924,7 @@
925
924
  "entity": "app_session",
926
925
  "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.",
927
926
  "guidance": [
928
- "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."
927
+ "On Android the height is the WHOLE screen including the status bar \u2014 2340 on a Galaxy S24 whose usable area starts 91px down \u2014 so the top strip of that rectangle is not the app's. On iOS it is the app's window in points."
929
928
  ],
930
929
  "path_params": [
931
930
  {
@@ -1453,7 +1452,7 @@
1453
1452
  "entity": "app_session",
1454
1453
  "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.",
1455
1454
  "guidance": [
1456
- "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.",
1455
+ "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 find_element with get_element_text for deciding what to act on.",
1457
1456
  "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.",
1458
1457
  "A screen the app marks secure comes back black, which is the app's doing and not a failure."
1459
1458
  ],
@@ -1489,8 +1488,8 @@
1489
1488
  "intent": "Go back one screen \u2014 use this to leave a detail page, close a dialog, or undo a navigation step.",
1490
1489
  "guidance": [
1491
1490
  "On Android this is the system back button and is the reliable way back.",
1492
- "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.",
1493
- "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.",
1491
+ "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 by finding an element of the screen you expect rather than from the status.",
1492
+ "Pressing back on the first screen of an app can send the app to the background and show the home screen; find one of the app's own elements afterwards to confirm where you are \u2014 \"no such element\" means it went to the background.",
1494
1493
  "The response is null on success and says nothing about whether the screen actually changed.",
1495
1494
  "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."
1496
1495
  ],
@@ -1560,7 +1559,7 @@
1560
1559
  "guidance": [
1561
1560
  "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.",
1562
1561
  "The value must be upper case, PORTRAIT or LANDSCAPE.",
1563
- "Element positions and the window rectangle change after a rotation, so re-read the page source before acting on coordinates.",
1562
+ "Element positions and the window rectangle change after a rotation, so re-read get_element_rect and get_window_rect before acting on coordinates.",
1564
1563
  "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."
1565
1564
  ],
1566
1565
  "path_params": [
@@ -1641,7 +1640,7 @@
1641
1640
  "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.",
1642
1641
  "guidance": [
1643
1642
  "Use a name exactly as list_contexts returned it; an invented WEBVIEW name is rejected.",
1644
- "In a web view, selectors change: find_element then takes css selector and the page source is HTML rather than the native UI tree.",
1643
+ "In a web view, selectors change: find_element then takes css selector and get_element_text reads the page's text rather than a native label.",
1645
1644
  "Switch back to NATIVE_APP before driving native controls again.",
1646
1645
  "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."
1647
1646
  ],
@@ -2456,7 +2455,7 @@
2456
2455
  "The session must have been started with enable_biometric true; it cannot be turned on once the session is running.",
2457
2456
  "Call it only while the prompt is actually on screen \u2014 with no prompt showing it fails with BROWSERSTACK_COMMAND_EXECUTION_FAILED.",
2458
2457
  "biometricMatch takes \"pass\" or \"fail\"; \"fail\" is how to exercise the app's rejection path.",
2459
- "Read the page source afterwards to see what the app did with the answer.",
2458
+ "Afterwards, find the element the app shows on success or on failure to see what it did with the answer.",
2460
2459
  "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."
2461
2460
  ],
2462
2461
  "path_params": [
@@ -2649,7 +2648,7 @@
2649
2648
  "guidance": [
2650
2649
  "iOS only; press_keycode is the Android equivalent and the two are not interchangeable.",
2651
2650
  "The button names carry no separator: home, volumeup, volumedown.",
2652
- "Pressing home sends the app to the background \u2014 bring it back with launch_app, whose args object takes bundleId rather than appId on iOS, and confirm from the page source that the app is in front again.",
2651
+ "Pressing home sends the app to the background \u2014 bring it back with launch_app, whose args object takes bundleId rather than appId on iOS, and confirm the app is in front again by finding one of its elements.",
2653
2652
  "The response is null on success and says nothing about what the button did."
2654
2653
  ],
2655
2654
  "path_params": [
@@ -2996,6 +2995,144 @@
2996
2995
  "$response": "WebDriverError"
2997
2996
  }
2998
2997
  }
2998
+ },
2999
+ {
3000
+ "name": "get_element_text",
3001
+ "method": "GET",
3002
+ "path": "/wd/hub/session/{session_id}/element/{element_id}/text",
3003
+ "mode": "read",
3004
+ "entity": "app_session",
3005
+ "intent": "Read the visible text of an element on the screen \u2014 use this to check a label, a title, a price, a field's current contents or an error message without reading the whole screen.",
3006
+ "guidance": [
3007
+ "THIS IS HOW TO READ THE SCREEN: find_element gives the id, this gives what it says \u2014 one line per call, nothing to wade through.",
3008
+ "On Android it is the text of the view; a container answers with an empty string, not its children's text, so aim at the TextView itself (class name android.widget.TextView, or the element by its own text).",
3009
+ "On iOS it is the element's label or value \u2014 a text field answers with what it currently holds, or its placeholder when empty.",
3010
+ "To confirm an action did something, read the same element before and after, or find the text you expect with find_element by text and treat \"no such element\" as the answer."
3011
+ ],
3012
+ "path_params": [
3013
+ {
3014
+ "name": "session_id",
3015
+ "type": "string",
3016
+ "required": true,
3017
+ "example": "0123456789abcdef0123456789abcdef01234567"
3018
+ },
3019
+ {
3020
+ "name": "element_id",
3021
+ "type": "string",
3022
+ "required": true,
3023
+ "description": "Element id from find_element or find_elements.",
3024
+ "example": "00000000-0000-0001-ffff-ffff00000012"
3025
+ }
3026
+ ],
3027
+ "returns": [
3028
+ "value"
3029
+ ],
3030
+ "responses": {
3031
+ "200": {
3032
+ "description": "The element's visible text.",
3033
+ "schema": {
3034
+ "$schema": "StringValueResponse"
3035
+ }
3036
+ },
3037
+ "404": {
3038
+ "$response": "WebDriverError"
3039
+ }
3040
+ }
3041
+ },
3042
+ {
3043
+ "name": "get_element_rect",
3044
+ "method": "GET",
3045
+ "path": "/wd/hub/session/{session_id}/element/{element_id}/rect",
3046
+ "mode": "read",
3047
+ "entity": "app_session",
3048
+ "intent": "Get an element's position and size on the screen \u2014 use this to aim a swipe, drag or tap with perform_actions, or to tell whether something sits under the keyboard.",
3049
+ "guidance": [
3050
+ "The coordinates are in the same space perform_actions uses: device pixels on Android, points on iOS \u2014 so x + width / 2 and y + height / 2 is the element's centre, ready to press.",
3051
+ "One call per element: the coordinates to aim at, without reading the whole screen.",
3052
+ "An element that is off screen still has a rect \u2014 one outside get_window_rect's size \u2014 so compare the two before tapping; scroll_gesture or scroll_view brings it on."
3053
+ ],
3054
+ "path_params": [
3055
+ {
3056
+ "name": "session_id",
3057
+ "type": "string",
3058
+ "required": true,
3059
+ "example": "0123456789abcdef0123456789abcdef01234567"
3060
+ },
3061
+ {
3062
+ "name": "element_id",
3063
+ "type": "string",
3064
+ "required": true,
3065
+ "description": "Element id from find_element or find_elements.",
3066
+ "example": "00000000-0000-0001-ffff-ffff00000012"
3067
+ }
3068
+ ],
3069
+ "returns": [
3070
+ "value.x",
3071
+ "value.y",
3072
+ "value.width",
3073
+ "value.height"
3074
+ ],
3075
+ "responses": {
3076
+ "200": {
3077
+ "description": "The element's rectangle on screen.",
3078
+ "schema": {
3079
+ "$schema": "RectResponse"
3080
+ }
3081
+ },
3082
+ "404": {
3083
+ "$response": "WebDriverError"
3084
+ }
3085
+ }
3086
+ },
3087
+ {
3088
+ "name": "get_element_attribute",
3089
+ "method": "GET",
3090
+ "path": "/wd/hub/session/{session_id}/element/{element_id}/attribute/{name}",
3091
+ "mode": "read",
3092
+ "entity": "app_session",
3093
+ "intent": "Read one attribute of an element \u2014 use this for its resource id, content description, enabled, checked, selected or displayed state, or on iOS its name, label, value or visibility.",
3094
+ "guidance": [
3095
+ "The attribute names are the platform's own: on Android text, content-desc, resource-id, class, package, bounds, checked, checkable, clickable, enabled, focused, selected, displayed, scrollable, long-clickable, password; on iOS name, label, value, type, enabled, visible, accessible, focused, index, rect.",
3096
+ "Booleans come back as the strings \"true\" and \"false\", not JSON booleans, so compare as text.",
3097
+ "An attribute the element does not carry answers the STRING \"null\" on Android \u2014 not a JSON null and not an error \u2014 so compare as text; measured on an ImageView's resource-id.",
3098
+ "For the element's text use get_element_text and for its position get_element_rect; this is for everything else."
3099
+ ],
3100
+ "path_params": [
3101
+ {
3102
+ "name": "session_id",
3103
+ "type": "string",
3104
+ "required": true,
3105
+ "example": "0123456789abcdef0123456789abcdef01234567"
3106
+ },
3107
+ {
3108
+ "name": "element_id",
3109
+ "type": "string",
3110
+ "required": true,
3111
+ "description": "Element id from find_element or find_elements.",
3112
+ "example": "00000000-0000-0001-ffff-ffff00000012"
3113
+ },
3114
+ {
3115
+ "name": "name",
3116
+ "type": "string",
3117
+ "required": true,
3118
+ "description": "The attribute to read, as named in the UI tree.",
3119
+ "example": "content-desc"
3120
+ }
3121
+ ],
3122
+ "returns": [
3123
+ "value"
3124
+ ],
3125
+ "responses": {
3126
+ "200": {
3127
+ "description": "The attribute's value as a string; an absent one is the string \"null\" on Android.",
3128
+ "schema": {
3129
+ "$schema": "NullableStringResponse"
3130
+ }
3131
+ },
3132
+ "404": {
3133
+ "$response": "WebDriverError"
3134
+ }
3135
+ }
2999
3136
  }
3000
3137
  ],
3001
3138
  "schemas": {
@@ -3458,6 +3595,15 @@
3458
3595
  "description": "Always empty on success."
3459
3596
  }
3460
3597
  }
3598
+ },
3599
+ "NullableStringResponse": {
3600
+ "type": "object",
3601
+ "properties": {
3602
+ "value": {
3603
+ "type": "string",
3604
+ "description": "Null when the attribute is absent."
3605
+ }
3606
+ }
3461
3607
  }
3462
3608
  },
3463
3609
  "responses": {
@@ -3534,6 +3680,9 @@
3534
3680
  "get_active_app_info",
3535
3681
  "read_device_property",
3536
3682
  "find_elements",
3683
+ "get_element_text",
3684
+ "get_element_rect",
3685
+ "get_element_attribute",
3537
3686
  "clear_element",
3538
3687
  "scroll_gesture",
3539
3688
  "dismiss_keyboard_key",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserstack/mcp-server",
3
- "version": "2.3.0-beta.6",
3
+ "version": "2.3.0-beta.7",
4
4
  "description": "BrowserStack's Official MCP Server",
5
5
  "mcpName": "io.github.browserstack/mcp-server",
6
6
  "main": "dist/index.js",