Botmation Documentation
Conditionals
Conditionals
Botmation has a special BotAction's that assemble BotAction's that run if a condition is met.
Functional if statement
Let's get started with the simplest one, called givenThat()()
, a functional if statement.
Here's a simplified bit of code from the Instagram example that attempts to login, only if the bot is a Guest:
await chain( log('Bot is running aka User'),
givenThat(isGuest)( log('is guest so logging in'), login({username: 'account', password: 'password'}), ),
givenThat(isLoggedIn)( log('Bot is logged in'), screenshot('logged-in-view') ))(page)
givenThat()()
returns a composed BotAction that runs assembled BotAction's only if the BotAction in the first call givenThat()
resolves true
.
All Branching BotActions run assembled BotAction's in a Pipe
Conditional BotAction
The first call givenThat()
accepts a special kind of BotAction called ConditionalBotAction. ConditionalBotAction are like BotAction's, except they always return a boolean value.
isGuest
and isLoggedIn
are ConditionalBotAction's for Instagram's web app. They are not intended to change the state of the page
but check the page
for a condition, then report on it. This way, the Instagram login()
BotAction is ran only if needed. Check the Instagram example for more including saving & loading cookies, to skip subsequent logins.
For a more technical explanation, givenThat()()
returns a customized BotAction for the whole flow. When you resolve the returned BotAction, it runs through a series of steps. First, it resolves the ConditionalBotAction
in a Pipe for the value it returns. Then runs the assembled BotAction's if the value was true
. However, it does not return the final pipe value to be compatible with Chain's.
Conditionals evaluating Pipe Value
There are other BotAction's like givenThat()()
for running a line of BotActions if a condition passes. They evaluate the equality of values supplied against the Pipe object value. They are called pipeCase()() && pipeCases()() which run the assembled BotAction's of their second call, if their condition in the first call passes.
Conditional Callback
Instead of handling a ConditionalBotAction in their first call, they expect a Pipe value, which can be almost anything, including a synchronous function. When a synchronous function is provided, it's called as a callback with the Pipe value as its only parameter. These functions are called ConditionalCallback's. Here is the interface:
interface ConditionalCallback<V = PipeValue> extends Function { (value: V) : boolean}
They take a PipeValue and operate on it to return a boolean value, representing if the condition past or not.
These BotAction's support multiple values, and handle their overall "if condition" differently. pipeCase()() will test all of its values as separate expressions to match against the Pipe value. If one or more of those expressions evaluates as true
then it runs its assembled BotAction's. In essence, it combines each value in the conditional expression with ||
.
pipeCases()(), on the otherhand, will test all of its values until one of them evaluates as false
, breaking the condition evaluation. All of its values must match the Pipe value, in order for the assembled BotAction's to run. In essence, it combines each value in the conditional expression with &&
.
With this approach, you can supply simple sync functions to create conditional expressions to determine whether or not some functionality needs to run. Both BotAction's will pipe in the same Pipe object that they are testing into their first assembled BotAction's. Therefore, it's possible to nest these to create more elaborate "if conditions" that combine or/and ||
/&&
.
Finally, you can compose these BotAction's with a Switch Pipe, to create a switch, case(s), and break(s) (with the Abort BotAction) flow to handle more use-cases. For an example of all three combined, see LinkedIn's likeUserPostsFrom() BotAction.
While Switch Pipe, Pipe Case, Pipe Cases, and Abort were all created together to work together in this particular way, they are all BotAction's that can be assembled with or without each other.
Cases Signal
Pipe Case and Pipe Cases return an unique object called CasesSignal
, unless they are aborted completely. The CasesSignal has an important key called conditionPass
which is a boolean to explain whether or not their condition evaluated as true
. Also, CasesSignal includes the values that matched the pipe object value, including the functions, on a matches
key. It's a simple JSON object with key->value pairs. The keys are the index'es of the values matched, set to their corresponding values. If you need to know which cases of Pipe Case matched, you can read the returned CasesSignal matches
object.
type CasesSignal<V = any> = { brand: 'Cases_Signal', matches: Dictionary<V>, conditionPass: boolean, pipeValue?: PipeValue}
CasesSignal is recognized by Switch Pipe, and once a CasesSignal conditionPass
is true
, Switch Pipe lowers its assembledLines
for aborting by one. That's how abort()
does not break a Switch Pipe line, until a Pipe Case(s) has returned a CasesSignal with conditionPass
as true
. Therefore, it mirrors the behavior of a traditional switch, case, break code block. For an example, see LinkedIn's likeUserPostsFrom() BotAction.