Skip to main content
Version: Latest

Conditional Actions

Conditional actions are essential for robust automation. They allow your automation to adapt to different scenarios and handle dynamic UI elements. This guide shows the modern approach using ConditionalActionChain and action chaining.

Legacy API Notice

This guide has been updated to use the modern ConditionalActionChain API and ActionConfig hierarchy. If you're looking for information on the legacy RepeatUntilConfig with ActionOptions, see the Upgrading to Latest guide.

Required Importsโ€‹

All examples in this guide require these imports:

import io.github.jspinak.brobot.action.Action;
import io.github.jspinak.brobot.action.ActionChainOptions;
import io.github.jspinak.brobot.action.ConditionalActionChain;
import io.github.jspinak.brobot.action.ActionResult;
import io.github.jspinak.brobot.action.ObjectCollection;
import io.github.jspinak.brobot.action.basic.click.ClickOptions;
import io.github.jspinak.brobot.action.basic.find.PatternFindOptions;
import io.github.jspinak.brobot.action.basic.type.TypeOptions;
import io.github.jspinak.brobot.action.internal.execution.ActionChainExecutor;
import io.github.jspinak.brobot.datatypes.state.stateObject.stateImage.StateImage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Setup: Dependency Injectionโ€‹

All examples assume this setup:

@Component
public class MyConditionalActions {
private static final Logger logger = LoggerFactory.getLogger(MyConditionalActions.class);

@Autowired
private Action action;

@Autowired
private ActionChainExecutor chainExecutor;

// Your conditional action methods go here...
}

Note: Examples using ConditionalActionChain only need action. Examples using ActionChainOptions also need chainExecutor.

StateImage Initializationโ€‹

Before using conditional actions, create your StateImages:

// In your @State class or configuration
StateImage buttonToClick = new StateImage.Builder()
.setName("next-button")
.addPattern("images/buttons/next.png")
.build();

StateImage imageToAppear = new StateImage.Builder()
.setName("success-message")
.addPattern("images/messages/success.png")
.build();

StateImage deleteButton = new StateImage.Builder()
.setName("delete-button")
.addPattern("images/buttons/delete.png")
.build();

These StateImages are then passed to your conditional action methods.

Modern Conditional Action APIsโ€‹

Brobot provides several approaches for conditional actions:

  1. ConditionalActionChain - Fluent API for most conditional workflows (recommended)
  2. ActionConfig.then() - Chain actions with implicit conditions
  3. ActionChainOptions with setMaxRepetitions() - Low-level control for complex patterns

This guide focuses on ConditionalActionChain, the most intuitive and powerful approach.

ConditionalActionChain Overviewโ€‹

ConditionalActionChain provides:

  • Fluent, readable syntax for conditional logic
  • Built-in retry and repetition patterns
  • Automatic error handling with conditional branches
  • Integration with keyboard shortcuts and form operations
  • No need for manual repeat-until configuration

Basic Click-Until Patternsโ€‹

Click Until Image Appearsโ€‹

The most common pattern is clicking a button until something appears:

public boolean clickUntilImageAppears(StateImage buttonToClick, StateImage imageToAppear) {
// Modern approach using ConditionalActionChain
// Each .ifNotFoundClick() represents one retry attempt
return ConditionalActionChain
.find(buttonToClick)
.ifFoundClick()
.then(imageToAppear) // Check if target image appears
.ifFoundLog("Success! Image appeared")
.ifNotFoundClick(buttonToClick) // Retry 1
.ifNotFoundClick(buttonToClick) // Retry 2
.ifNotFoundClick(buttonToClick) // Retry 3
.ifNotFoundClick(buttonToClick) // Retry 4
.ifNotFoundClick(buttonToClick) // Retry 5
.perform(action)
.isSuccess();
}

For many retries, use ActionChainOptions instead:

public boolean clickUntilImageAppearsMany(StateImage buttonToClick, StateImage imageToAppear) {
// Better for 10+ retries
ActionChainOptions clickWithRetries = new ActionChainOptions.Builder(
new ClickOptions.Builder()
.setPauseAfterEnd(0.5)
.build())
.setMaxRepetitions(10) // Up to 10 clicks
.setStopCondition(result -> {
// Stop when imageToAppear is found
ActionResult checkResult = action.find(imageToAppear);
return checkResult.isSuccess();
})
.build();

ActionResult result = chainExecutor.executeChain(
clickWithRetries,
new ActionResult(),
buttonToClick.asObjectCollection()
);

return result.isSuccess();
}

Alternative with explicit find configuration:

public boolean clickUntilImageAppearsConfigured(StateImage buttonToClick, StateImage imageToAppear) {
PatternFindOptions findButton = new PatternFindOptions.Builder()
.setSimilarity(0.8)
.setPauseAfterEnd(0.5)
.build();

PatternFindOptions findImage = new PatternFindOptions.Builder()
.setSearchDuration(2.0)
.setSimilarity(0.85)
.build();

return ConditionalActionChain
.find(findButton)
.ifFoundClick()
.then(findImage)
.ifNotFoundClick(buttonToClick) // Retry pattern
.perform(action, new ObjectCollection.Builder()
.withImages(buttonToClick, imageToAppear)
.build())
.isSuccess();
}

Click Until Elements Vanishโ€‹

Sometimes you need to click elements until they disappear:

public boolean clickUntilElementsVanish(StateImage elementToClick) {
// Click the element and check if it vanishes
return ConditionalActionChain
.find(elementToClick)
.ifFoundClick()
.then(elementToClick) // Check if still visible
.ifFoundClick() // Still there, click again
.ifFoundClick() // Retry
.ifFoundClick() // Retry
.ifNotFoundLog("Element vanished successfully")
.perform(action, elementToClick.asObjectCollection())
.isSuccess();
}

Alternative using ActionChainOptions for precise control:

public boolean clickUntilVanish(StateImage deleteButton) {
// Use setStopCondition to check when element is gone
ActionChainOptions clickUntilGone = new ActionChainOptions.Builder(
new ClickOptions.Builder()
.setPauseAfterEnd(0.5)
.build())
.setMaxRepetitions(10)
.setStopCondition(result -> result.getMatchList().isEmpty()) // Stop when no matches
.build();

ActionResult result = chainExecutor.executeChain(
clickUntilGone,
new ActionResult(),
deleteButton.asObjectCollection()
);

return result.isSuccess();
}

Advanced Patternsโ€‹

Multi-Step Navigationโ€‹

Click through a wizard until the finish button appears:

public boolean clickNextUntilFinishAppears(StateImage nextButton, StateImage finishButton) {
// Use ActionChainOptions for wizard navigation with many steps
ClickOptions clickNext = new ClickOptions.Builder()
.setPauseAfterEnd(1.0) // Pause for page transitions
.build();

ActionChainOptions wizardNavigation = new ActionChainOptions.Builder(clickNext)
.setMaxRepetitions(10) // Maximum 10 wizard pages
.setStopCondition(result -> {
// Stop when finish button appears
PatternFindOptions findFinish = new PatternFindOptions.Builder()
.setSimilarity(0.85)
.setSearchDuration(3.0)
.build();
ActionResult finishCheck = action.perform(findFinish, finishButton.asObjectCollection());
return finishCheck.isSuccess();
})
.build();

ActionResult result = chainExecutor.executeChain(
wizardNavigation,
new ActionResult(),
nextButton.asObjectCollection()
);

return result.isSuccess();
}

Alternative: Simple wizard with few steps:

public boolean clickNextFewTimes(StateImage nextButton, StateImage finishButton) {
// For wizards with 3-5 steps, ConditionalActionChain is clearer
return ConditionalActionChain
.find(nextButton)
.ifFoundClick()
.then(finishButton)
.ifNotFoundClick(nextButton) // Page 2
.ifNotFoundClick(nextButton) // Page 3
.ifNotFoundClick(nextButton) // Page 4
.ifNotFoundClick(nextButton) // Page 5
.perform(action, new ObjectCollection.Builder()
.withImages(nextButton, finishButton)
.build())
.isSuccess();
}

Clearing Dynamic Listsโ€‹

Remove all items from a list by clicking delete buttons:

public boolean clearAllItems(StateImage deleteButton) {
ActionChainOptions clickUntilNone = new ActionChainOptions.Builder(
new ClickOptions.Builder()
.setPauseAfterEnd(0.3)
.build())
.setMaxRepetitions(50) // Higher limit for large lists
.setStopCondition(result -> result.getMatchList().isEmpty())
.build();

ActionResult result = chainExecutor.executeChain(
clickUntilNone,
new ActionResult(),
deleteButton.asObjectCollection()
);

return result.isSuccess();
}

Polling for Status Changesโ€‹

Wait for a process to complete by checking status:

public boolean waitForProcessComplete(StateImage refreshButton, StateImage completeStatus) {
// Use ActionChainOptions for polling with timeout
ClickOptions clickRefresh = new ClickOptions.Builder()
.setPauseAfterEnd(2.0) // Wait 2 seconds between refreshes
.build();

ActionChainOptions pollingChain = new ActionChainOptions.Builder(clickRefresh)
.setMaxRepetitions(30) // 30 refreshes ร— 2 seconds = 1 minute max
.setStopCondition(result -> {
// Stop when complete status appears
PatternFindOptions findComplete = new PatternFindOptions.Builder()
.setSearchDuration(1.0)
.build();
ActionResult statusCheck = action.perform(findComplete, completeStatus.asObjectCollection());
return statusCheck.isSuccess();
})
.build();

ActionResult result = chainExecutor.executeChain(
pollingChain,
new ActionResult(),
refreshButton.asObjectCollection()
);

return result.isSuccess();
}

Alternative: Quick status check without clicking:

public boolean waitForStatusChange(StateImage completeStatus, int maxSeconds) {
// Poll for status without clicking refresh button
PatternFindOptions checkStatus = new PatternFindOptions.Builder()
.setSearchDuration(1.0)
.setPauseAfterEnd(1.0) // Check every second
.build();

ActionChainOptions statusPolling = new ActionChainOptions.Builder(checkStatus)
.setMaxRepetitions(maxSeconds) // Check N times
.setStopCondition(result -> result.isSuccess()) // Stop when found
.build();

ActionResult result = chainExecutor.executeChain(
statusPolling,
new ActionResult(),
completeStatus.asObjectCollection()
);

return result.isSuccess();
}

Form Automation Patternsโ€‹

Type and Verifyโ€‹

Try typing a value and verify it was accepted:

public boolean typeAndVerify(StateImage inputField, String value, StateImage successIndicator) {
return ConditionalActionChain
.find(inputField)
.ifFoundClick()
.ifFoundClearAndType(value) // Built-in clear and type
.then(successIndicator)
.ifFoundLog("Value accepted: " + value)
.ifNotFoundLog("Value rejected: " + value)
.perform(action, new ObjectCollection.Builder()
.withImages(inputField, successIndicator)
.withStrings(value)
.build())
.isSuccess();
}

Try Multiple Valuesโ€‹

Try different values until one is accepted:

public boolean typeUntilAccepted(StateImage inputField,
List<String> possibleValues,
StateImage successIndicator) {
// Try each value until one succeeds
for (String value : possibleValues) {
if (typeAndVerify(inputField, value, successIndicator)) {
return true;
}
}
return false;
}

Complex Conditional Logicโ€‹

Multiple Termination Conditionsโ€‹

Stop when either of two conditions is met:

public boolean performUntilEitherCondition(StateImage actionTarget,
StateImage condition1,
StateImage condition2) {
// Try to find either condition after clicking
return ConditionalActionChain
.find(actionTarget)
.ifFoundClick()
.then(condition1)
.ifFoundLog("Condition 1 met")
.ifNotFoundThen(condition2) // Check second condition
.ifFoundLog("Condition 2 met")
.ifNotFoundClick(actionTarget) // Retry if neither found
.perform(action, new ObjectCollection.Builder()
.withImages(actionTarget, condition1, condition2)
.build())
.isSuccess();
}

Nested Conditionsโ€‹

Handle complex scenarios with nested patterns:

public boolean complexConditionalWorkflow(StateImage menuButton,
StateImage submenu,
StateImage targetOption) {
// Open menu until submenu appears, then click target
return ConditionalActionChain
.find(menuButton)
.ifFoundClick()
.then(submenu)
.ifFoundClick(targetOption) // Submenu found, click target
.ifNotFoundClick(menuButton) // Submenu not found, retry menu
.ifNotFoundClick(menuButton)
.ifNotFoundClick(menuButton)
.perform(action, new ObjectCollection.Builder()
.withImages(menuButton, submenu, targetOption)
.build())
.isSuccess();
}

Best Practicesโ€‹

1. Use ConditionalActionChain for Readabilityโ€‹

ConditionalActionChain provides the clearest intent:

// Clear and readable
ConditionalActionChain
.find(button)
.ifFoundClick()
.then(result)
.ifNotFoundLog("Operation failed")
.perform(action);

2. Set Reasonable Retry Limitsโ€‹

Chain ifNotFound calls to control retry attempts:

  • Quick operations: 3-5 retries
  • Page transitions: 5-10 retries
  • Long processes: 10-30 retries

3. Add Appropriate Delaysโ€‹

Configure delays in ClickOptions or PatternFindOptions:

ClickOptions withDelay = new ClickOptions.Builder()
.setPauseAfterEnd(1.0) // 1 second delay
.build();

4. Use Built-In Form Operationsโ€‹

ConditionalActionChain has specialized form methods:

  • .ifFoundClearAndType() - Clear field then type
  • .ifFoundType() - Type without clearing
  • .pressEnter(), .pressEscape(), .pressCtrlS() - Keyboard shortcuts

5. Handle Failures Gracefullyโ€‹

Use .ifNotFound() methods for error handling:

return ConditionalActionChain
.find(element)
.ifFoundClick()
.ifNotFoundLog("Element not found after retries")
.ifNotFoundThen(fallbackElement) // Try alternative
.perform(action)
.isSuccess();

6. Prefer Conditional Chains Over Manual Loopsโ€‹

// โŒ Avoid manual loops
for (int i = 0; i < 10; i++) {
if (action.find(element).isSuccess()) {
action.click(element);
break;
}
}

// โœ… Use ConditionalActionChain
ConditionalActionChain
.find(element)
.ifFoundClick()
.ifNotFound(element) // Automatic retry
.perform(action);

7. Monitor Performance with ActionResultโ€‹

Check intermediate results when needed:

ActionResult result = ConditionalActionChain
.find(button)
.ifFoundClick()
.perform(action);

logger.info("Found {} matches in {}ms",
result.getMatchList().size(),
result.getDuration());

Keyboard Shortcutsโ€‹

ConditionalActionChain provides built-in keyboard operations:

// Save with Ctrl+S
ConditionalActionChain
.find(document)
.ifFoundClick()
.pressCtrlS() // Built-in shortcut
.perform(action);

// Cancel with Escape
ConditionalActionChain
.find(dialog)
.ifFoundPressEscape()
.perform(action);

// Submit with Enter
ConditionalActionChain
.find(form)
.ifFoundPressEnter()
.perform(action);

When to Use Each Approachโ€‹

Use ConditionalActionChain when:โ€‹

  • Retries needed: 1-5 retry attempts (use .ifNotFound() chains)
  • Conditional logic: Different actions based on find results
  • Form automation: Filling fields, validating input
  • Readability: Declarative, self-documenting code
  • Built-in shortcuts: Need keyboard operations (.pressEnter(), .pressEscape())

Example: Click-until-appears with 3-5 retries, form filling, conditional workflows

Use ActionChainOptions when:โ€‹

  • Many retries: 10+ repetitions needed
  • Custom conditions: Complex setStopCondition() logic
  • Polling patterns: Status checks with timeouts
  • Wizard navigation: Multi-step processes with unknown length
  • Precise control: Need exact repetition count and timing

Example: Wizard navigation (10+ pages), polling (30+ checks), clearing dynamic lists (50+ items)

Use ActionConfig.then() when:โ€‹

  • Sequential only: No conditional branching needed
  • Simple chains: 2-3 actions in sequence
  • No retries: Actions run once
  • Embedded in chains: Part of larger ConditionalActionChain or ActionChainOptions

Example: Find โ†’ Click โ†’ Verify sequence without retries

Decision Treeโ€‹

Need conditional logic?
โ”œโ”€ Yes โ†’ How many retries?
โ”‚ โ”œโ”€ 1-5 retries โ†’ ConditionalActionChain
โ”‚ โ””โ”€ 10+ retries โ†’ ActionChainOptions with setMaxRepetitions
โ””โ”€ No โ†’ Simple sequence?
โ”œโ”€ Yes โ†’ ActionConfig.then()
โ””โ”€ No โ†’ ActionChainOptions with custom logic

Migration from Legacy APIsโ€‹

From ActionOptions to ActionConfigโ€‹

// โŒ Old - ActionOptions (deprecated, doesn't exist)
ActionOptions clickOptions = new ActionOptions.Builder()
.setAction(ActionType.CLICK)
.build();

// โœ… New - ClickOptions
ClickOptions clickOptions = new ClickOptions.Builder()
.build();

From RepeatUntilConfig to ConditionalActionChainโ€‹

// โŒ Old - RepeatUntilConfig pattern
RepeatUntilConfig config = new RepeatUntilConfig.Builder()
.setDoAction(clickOptions)
.setUntilAction(findOptions)
.setMaxActions(10)
.build();
// executeRepeatUntil() doesn't exist!

// โœ… New - ConditionalActionChain
ConditionalActionChain
.find(button)
.ifFoundClick()
.ifNotFoundClick(button) // Retry pattern
.perform(action);

See the Upgrading to Latest guide for comprehensive migration instructions.

Next Stepsโ€‹

Reference Documentationโ€‹