FlashbackMacros can support branching behaviors based on the visual state of the application. This can be achived by editing config.json
or writing a custom script.
You can add branching to your macros by editing the config.json
file. Each action in the config.json
file can have a list of next action names. When the macro reaches an action, it will check the list of next action names and choose one to execute.
For example, the following config.json
file defines a macro with two branches:
{
"actions": [
{
"name": "click_gamble",
"next_action_names": ["result_win", "result_lose"]
},
{
"name": "result_win",
"next_action_names": ["result_winnings"]
},
{
"name": "result_lose",
"next_action_names": ["click_gamble"]
}
]
}
This macro has three actions: click_gamble
, result_win
, and result_lose
. After the macro performs the click_gamble
action, it will check the list of next action names and choose one to execute based on the visual state of the app. In this example, the macro branches to handle the win and loss scenarios.
You can also write a custom runner to run your macro in a different way. The default runner, Runner
, simply executes the actions in the config.json
file in order. However, you could write a custom runner to do things like:
- Choose the next action based on the result of a previous action.
- Adding custom logging, saving screenshots or other behaviors like making HTTP requests.
- Retry failed actions.
- Terminate the macro on certain conditions.
To write a custom runner, you can use the Executor
class. The Executor
class has methods for executing actions, pausing the macro, resuming the macro, retrying failed actions, and terminating the macro.
This runner takes a timeout argument, which specifies the maximum runtime of the macro in minutes. If the macro does not finish running within the timeout period, the runner will stop the macro and exit.
from tools.runner import Runner
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(dest='timeout', type=float, help="Maximum runtime in minutes")
args = parser.parse_args()
r = Runner(config_name="CONFIG_NAME", device_name="DEVICE_NAME")
r.run(next_actions=["click_1"], exit_actions=["click_5"], min_action_delay=1, state = {"viability_adjustment": 10}, max_minutes=args.timeout)
The Runner
will return control to the caller when:
- It reaches an empty
next_action_names
- It performs an action in
exit_actions
- The total runtime reaches the value provided in the optional
max_minutes
argument.
If you provide a non-empty exit_actions
and the script cannot reach it, it will throw UnreachableExitActionException
.
This is a script that uses FlashbackMacros launch an app and dismiss an optional modal.
from fbmr.devices import all_device_constructors
from fbmr.config import Config
from fbmr.executor import Executor
config = Config("configs", "MyApp_DismissModal")
executor = Executor()
executor.set_config(config)
device = all_device_constructors()["ANDROID_DEVICE_NAME"]()
utils = {"device": device}
# kill the app if it was already open and recompute the window size in case orientation changed
device.close_app(BUNDLE_ID)
device.open_app(BUNDLE_ID)
device.recompute_size(delay=5)
# launch the app and click the prompts until you get to the homescreen
executor.execute_chain("launch_app_1", ["launch_app_4"], state, utils, min_action_delay=delay)
# dismiss modal
image = device.screen_capture()
action = config.get_action("dismiss_modal_1")
viability = executor.check_conditions(action.conditions, f"Checking for modal", image, state, utils)
if viability != 0:
executor.execute_chain("dismiss_modal_1", ["dismiss_modal_3"], state, utils, min_action_delay=delay)
...