Use VoiceOver executors with Maestro
Learn how to use executors to automate VoiceOver gestures, capture spoken output, and add accessibility assertions in Maestro flows on real iOS devices.
Screen Reader Automation is available only under the Ultimate plan. For more details, check the pricing page.
BrowserStack App Accessibility provides three VoiceOver executors that you can call using a Maestro flow on iOS devices:
-
voiceOverToggleturns VoiceOver on or off. -
voiceOverGestureperforms one VoiceOver gesture, such as moving focus to the next element or activating the focused element. -
getVoiceOverElementreturns the most recent phrase from VoiceOver and the element that has the VoiceOver focus.
After your Maestro flow captures the returned values, you can check them with assertTrue assertions.
Appium tests call the same executors with the browserstack_executor command. If your tests use Appium, see Use executors to test screen reader interactions.
Prerequisites
Before you start, make sure you have the following:
- A BrowserStack username and access key. You can obtain your username and access key from the Account & Profile section on the dashboard.
If you haven’t created an account yet, sign up for a free trial. - An iOS app as an
.ipafile. - A Maestro test suite inside a single parent folder. For detailed steps, see Structure your Maestro accessibility tests.
Add the helper scripts to your test suite
BrowserStack provides a helper script for each executor. Save the three scripts in the same folder as the Maestro flow that calls them. The following example shows the folder structure:
flows/
├── voiceover-flow.yaml
├── bs_executor.js
├── bs_gesture.js
└── bs_get_element.js
Don’t edit the scripts. BrowserStack sets the session values that the scripts need, such as the session ID and device ID. Your flow passes the executor’s arguments to each script, as described in Call the executors from your flow.
VoiceOver toggle script
Save the following script as bs_executor.js to run the voiceOverToggle executor:
VoiceOver gesture script
Save the following script as bs_gesture.js to run the voiceOverGesture executor:
VoiceOver element script
Save the following script as bs_get_element.js to run the getVoiceOverElement executor and store the result in output.spokenDescription and output.element:
Call the executors from your flow
Add a runScript step to your Maestro flow for each executor call. Each step uses the following parameters:
-
file: the helper script file, for example,bs_executor.js. -
env: the executor’s arguments,BS_STATEforbs_executor.jsorBS_GESTUREforbs_gesture.js. -
label: the step name that appears in your Maestro logs.
Turn VoiceOver on or off
Call bs_executor.js with BS_STATE set to "on" or "off":
Turn VoiceOver on before you call the other executors. The executors work only in builds that set voiceOver to true. For details, see Run your build with VoiceOver.
Simulate VoiceOver gestures
Call bs_gesture.js with BS_GESTURE set to the gesture you want to perform:
Gesture values
Gesture names are case-sensitive. See the following table for a list of valid values:
| Gesture | Argument | Platform support |
|---|---|---|
| Move focus to next element | "navigate_next" |
Android and iOS |
| Move focus to previous element | "navigate_previous" |
Android and iOS |
| Activate focused element | "activate_item" |
Android and iOS |
| Scroll down | "scroll_down" |
Android and iOS |
| Scroll up | "scroll_up" |
Android and iOS |
| Navigate back or dismiss | "back" |
Android and iOS |
| Move focus to first element on screen | "home" |
Android and iOS |
| Move focus to first element on screen | "navigate_first" |
iOS |
| Move focus to last element on screen | "navigate_last" |
iOS |
| Read continuously from the top of the screen | "read_from_top" |
iOS |
| Read continuously from the current position | "read_from_current" |
iOS |
| Move to the next item for the current rotor setting | "rotor_next" |
iOS |
| Move to the previous item for the current rotor setting | "rotor_previous" |
iOS |
| Pause or resume speech | "pause_speech" |
iOS |
On iOS, the following gestures behave as described:
- The
homeandnavigate_firstgestures perform the same action of moving the screen reader focus to the first element on the screen. - The
read_from_topandread_from_currentgestures start continuous reading. Usepause_speechto pause or resume continuous reading.
Capture the focused element and spoken output
Call bs_get_element.js to capture the element that has the VoiceOver focus and the most recent phrase from VoiceOver:
The script takes no env values and always reads the element that has the VoiceOver focus.
Captured values
After the step runs, your flow can use the following captured values:
-
output.element: theelementobject as a JSON string. -
output.spokenDescription: thespokenDescriptiontext.
The element object and spokenDescription text come from the executor’s result, which has the following fields:
| Field | Description |
|---|---|
element.elementClass |
Class of the focused element, for example, UIButton. |
element.label |
The element’s accessibilityLabel. |
element.value |
The element’s accessibilityValue. |
element.hint |
The element’s accessibilityHint. |
element.identifier |
The element’s accessibilityIdentifier. |
element.traits |
Raw UIAccessibilityTraits bitmask, as a number. |
element.frame |
On-screen position and size, in points. |
spokenDescription |
The most recent VoiceOver announcement, as plain text. |
In Maestro flows, element.traits is an array of trait names, such as ["Button"] or ["Link", "StaticText"], instead of a number.
output.spokenDescription and output.element are captured separately. If the spoken output can’t be captured, output.spokenDescription holds an empty string. If the element details can’t be captured, output.element holds {}. In either case, the step still succeeds, so add assertions only for the values that your test needs.
VoiceOver reads an element’s label, value, traits, and hint as separate phrases, so output.spokenDescription might hold only one of those phrases.
Add assertions
Add assertTrue steps to your Maestro flow to check output.spokenDescription and output.element, which hold the values from the most recent getVoiceOverElement call. The following steps check the spoken text and the element’s traits:
output.element is a JSON string, so use indexOf to check for a trait name.
Maestro flow example
The following flow turns VoiceOver on, moves focus through two elements, checks each one, and turns VoiceOver off:
Run your build with VoiceOver
To run your build with VoiceOver enabled, follow these steps:
- Zip your test suite folder with the helper scripts inside. For detailed steps, see Structure your Maestro accessibility tests.
- Upload your app and test suite. For detailed steps, see Integrate Maestro test suite with App Accessibility testing.
- Start the build with the iOS endpoint and
voiceOverset totrue:
With voiceOver set to true, your Maestro flow runs on a VoiceOver-enabled iOS device and can call the VoiceOver executors. If you don’t set voiceOver, the first executor step fails and the flow stops.
Check VoiceOver output and errors in your logs
Maestro builds run on BrowserStack App Automate, so the executor output appears in the Maestro Logs tab of your session on the App Automate dashboard. Use these logs to check the VoiceOver output for each element and to troubleshoot failed executor steps. For more information on Maestro logs, see View Maestro logs for your tests.
VoiceOver output
Each bs_get_element.js call adds the following to the Maestro Logs tab:
- The focused element as JSON
- The spoken description of that element
For example:
element {"label":"Send","value":"","hint":"Double tap to send your message","traits":["Button"],"elementClass":"AccessibilityNode","identifier":"","frame":{"x":24,"y":204,"width":345,"height":52}}
spokenDescription Send
The spoken output doesn’t appear in the Text Logs tab.
Executor errors
If an executor call returns an HTTP error, the step fails with an error message that starts with [EXECUTOR] and includes the HTTP status and response.
For example:
[EXECUTOR] voiceOverToggle on failed: HTTP <status> <response>
Related topics
We're sorry to hear that. Please share your feedback so we can do better
Contact our Support team for immediate help while we work on improving our docs.
We're continuously improving our docs. We'd love to know what you liked
We're sorry to hear that. Please share your feedback so we can do better
Contact our Support team for immediate help while we work on improving our docs.
We're continuously improving our docs. We'd love to know what you liked
Thank you for your valuable feedback!