Skip to main content
Version: Latest

Conditional Action Chains

Required Importsโ€‹

All examples in this guide assume the following imports:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.jspinak.brobot.action.Action;
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.type.TypeOptions;
import io.github.jspinak.brobot.action.basic.find.PatternFindOptions;
import io.github.jspinak.brobot.action.conditionals.ConditionalActionChain;
import io.github.jspinak.brobot.datatypes.state.stateObject.stateImage.StateImage;

Setup and Prerequisitesโ€‹

Component Setupโ€‹

All ConditionalActionChain examples require a Spring component with injected Action service:

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

@Autowired
private Action action;

// Your automation methods here
}

StateImage Initializationโ€‹

StateImages must be initialized before use. Example:

StateImage buttonImage = new StateImage.Builder()
.addPattern("images/buttons/my-button.png")
.setSimilarity(0.85)
.build();

For more on StateImage setup, see States in Brobot.

Assumed Variablesโ€‹

Assumed Variables

Unless otherwise specified, the following examples assume you have:

  • @Autowired Action action - Injected Brobot Action service
  • StateImage variables (e.g., buttonImage, menuButton) - Pre-initialized StateImage instances
  • Proper Spring component context with @Component annotation

See the Production Examples section for complete, compilable code.

Overviewโ€‹

ConditionalActionChain provides a powerful fluent API for building complex action sequences with conditional execution. This implementation includes the crucial then() method for sequential composition and numerous convenience methods.

Key Featuresโ€‹

  • Sequential Composition: The then() method enables multi-step workflows
  • Convenience Methods: Direct methods like click(), type(), scrollDown()
  • Keyboard Shortcuts: Built-in support for common key combinations
  • Control Flow: Methods for stopping chains, retrying, and error handling
  • No Explicit Waits: Follows model-based principles - timing via action configurations
  • Proper Conditional Logic: True if/then/else execution flow

Basic Examplesโ€‹

Simple Find and Clickโ€‹

// Basic find and click pattern
ConditionalActionChain.find(buttonImage)
.ifFoundClick()
.ifNotFoundLog("Button not found")
.perform(action, new ObjectCollection.Builder().build());

Sequential Actions with then()โ€‹

// The then() method enables sequential workflows
ConditionalActionChain.find(menuButton)
.ifFoundClick()
.then(searchField) // Move to next element
.ifFoundClick()
.ifFoundType("search query")
.then(submitButton) // Continue the flow
.ifFoundClick()
.perform(action, new ObjectCollection.Builder().build());

Real-World Scenariosโ€‹

Login Flowโ€‹

public ActionResult performLogin(String username, String password) {
return ConditionalActionChain.find(loginButton)
.ifFoundClick()
.ifNotFoundLog("Login button not visible")
.then(usernameField) // Sequential action using then()
.ifFoundClick()
.ifFoundType(username)
.then(passwordField) // Continue to next field
.ifFoundClick()
.ifFoundType(password)
.then(submitButton) // Move to submit
.ifFoundClick()
.then(successMessage) // Check for success
.ifFoundLog("Login successful!")
.ifNotFoundLog("Login might have failed")
.perform(action, new ObjectCollection.Builder().build());
}

Save with Confirmation Dialogโ€‹

public ActionResult saveWithConfirmation() {
StateImage saveButton = new StateImage.Builder()
.addPattern("images/buttons/save.png")
.build();

return ConditionalActionChain.find(saveButton)
.ifFoundClick()
.ifNotFoundLog("Save button not found")
.then(confirmDialog) // Look for confirmation
.then(yesButton) // Find yes button within dialog
.ifFoundClick()
.ifNotFoundLog("No confirmation needed")
.then(successMessage) // Verify success
.ifFoundLog("Save successful")
.ifNotFoundLog("Save may have failed")
.perform(action, new ObjectCollection.Builder().build());
}

Retry Patternโ€‹

public ActionResult clickWithRetry(StateImage target, int maxRetries) {
return ConditionalActionChain
.retry(new PatternFindOptions.Builder().build(), maxRetries)
.ifFoundClick()
.ifFoundLog("Successfully clicked after retries")
.ifNotFoundLog("Failed after all attempts")
.perform(action, new ObjectCollection.Builder()
.withImages(target)
.build());
}

Advanced Patternsโ€‹

Multi-Step Form Fillingโ€‹

public ActionResult fillComplexForm(FormData data) {
return ConditionalActionChain
.find(new PatternFindOptions.Builder().build())
.ifNotFoundLog("Form not visible")
.ifNotFoundDo(res -> { throw new RuntimeException("Cannot proceed without form"); })

// Name field - using clearAndType
.then(nameField)
.ifFoundClick()
.clearAndType(data.getName())

// Email field - using tab navigation
.pressTab()
.type(data.getEmail())

// Phone field - using direct navigation
.then(phoneField)
.ifFoundClick()
.ifFoundType(data.getPhone())

// Submit form
.then(submitButton)
.ifFoundClick()
.takeScreenshot("form-submission")
.perform(action, new ObjectCollection.Builder().build());
}

Dynamic UI Navigation with Scrollingโ€‹

public ActionResult scrollToFind(StateImage target) {
return ConditionalActionChain.find(target)
.ifNotFound(chain -> chain.scrollDown())
.ifNotFound(new PatternFindOptions.Builder().build())
.ifNotFound(chain -> chain.scrollDown())
.ifNotFound(new PatternFindOptions.Builder().build())
.ifNotFound(chain -> chain.scrollDown())
.ifFoundClick()
.ifFoundLog("Found and clicked target after scrolling")
.ifNotFoundLog("Could not find target even after scrolling")
.perform(action, new ObjectCollection.Builder()
.withImages(target)
.build());
}

Keyboard Shortcuts Workflowโ€‹

public ActionResult useKeyboardShortcuts() {
return ConditionalActionChain
.find(editorField)
.ifFoundClick()
.pressCtrlA() // Select all
.pressDelete() // Delete content
.type("New content here")
.pressCtrlS() // Save
.then(savedIndicator)
.ifFoundLog("Document saved successfully")
.perform(action, new ObjectCollection.Builder().build());
}

Conditional Patternsโ€‹

Error Handling with Control Flowโ€‹

public ActionResult handleErrors() {
return ConditionalActionChain
.find(submitButton)
.ifFoundClick()
.then(errorDialog)
.ifFoundLog("Error dialog appeared")
.ifFound(chain -> chain.takeScreenshot("error-state"))
.ifFoundDo(res -> {
log.error("Operation failed with error: {}", res.getText());
})
.stopIf(res -> res.getText() != null &&
!res.getText().isEmpty() &&
res.getText().get(0).contains("CRITICAL"))
.then(retryButton)
.ifFoundClick()
.ifFoundLog("Retrying operation")
.perform(action, new ObjectCollection.Builder().build());
}

Wait for Element to Disappearโ€‹

public ActionResult waitForLoadingToComplete() {
StateImage loadingSpinner = new StateImage.Builder()
.addPattern("images/indicators/loading.png")
.build();

return ConditionalActionChain
.find(submitButton)
.ifFoundClick()
.waitVanish(loadingSpinner) // Wait for spinner to disappear
.then(successMessage)
.ifFoundLog("Operation completed successfully")
.then(errorDialog)
.ifFoundLog("Operation failed")
.perform(action, new ObjectCollection.Builder().build());
}

Highlighting and Debuggingโ€‹

public ActionResult debugWorkflow() {
return ConditionalActionChain
.find(targetElement)
.ifFound(chain -> chain.highlight()) // Highlight found element
.ifFoundLog("Found target element") // Log for debugging
.takeScreenshot("debug-1") // Take screenshot
.ifFoundClick()
.takeScreenshot("debug-2") // Another screenshot
.perform(action, new ObjectCollection.Builder().build());
}

Model-Based Automation Principlesโ€‹

No Explicit Waitsโ€‹

Following model-based automation principles, ConditionalActionChain does not include a wait() method. Instead, timing is configured through action options:

// WRONG - Process-based approach with explicit waits
chain.click().wait(2.0).type("text") // Don't do this!

// CORRECT - Model-based approach with action configurations
PatternFindOptions findWithDelay = new PatternFindOptions.Builder()
.setPauseBeforeBegin(2.0) // Timing in action configuration
.build();

ConditionalActionChain.find(findWithDelay)
.ifFoundClick()
.then(new TypeOptions.Builder()
.setTypeDelay(0.1) // Type-specific timing
.build())
.perform(action, objectCollection);

State-Based Navigationโ€‹

public ActionResult navigateToState(State targetState) {
// Focus on states, not processes
return ConditionalActionChain
.find(targetState.getIdentifyingImage())
.ifFoundLog("Already in target state")
.ifNotFound(navigationButton)
.ifFoundClick()
.then(targetState.getIdentifyingImage())
.ifFoundLog("Successfully navigated to state")
.ifNotFoundDo(res -> {
throw new StateTransitionException("Failed to reach target state");
})
.perform(action, new ObjectCollection.Builder().build());
}

Testing Patternsโ€‹

Mock-Friendly Chainsโ€‹

@Test
public void testEnhancedChainFeatures() {
// Setup mock
Action mockAction = mock(Action.class);
ActionResult successResult = new ActionResult();
successResult.setSuccess(true);

when(mockAction.perform(any(ActionConfig.class), any(ObjectCollection[].class)))
.thenReturn(successResult);

// Test the then() method
ActionResult result = ConditionalActionChain
.find(loginButton)
.ifFoundClick()
.then(usernameField) // Sequential composition
.ifFoundType("testuser")
.then(passwordField) // Continue flow
.ifFoundType("password")
.perform(mockAction, new ObjectCollection.Builder().build());

assertTrue(result.isSuccess());
}

Complete API Referenceโ€‹

Core Methodsโ€‹

  • find(PatternFindOptions) / find(StateImage) - Start chain with find
  • then(ActionConfig) / then(StateImage) - Sequential action composition
  • ifFound(ActionConfig) - Execute if previous succeeded
  • ifNotFound(ActionConfig) - Execute if previous failed
  • always(ActionConfig) - Execute regardless

Convenience Methodsโ€‹

  • click() / ifFoundClick() - Click actions
  • type(String) / ifFoundType(String) - Type text
  • clearAndType(String) - Clear field and type
  • scrollDown() / scrollUp() - Scroll actions
  • highlight() - Highlight last found element
  • waitVanish(StateImage) - Wait for element to disappear

Keyboard Shortcutsโ€‹

  • pressEnter(), pressTab(), pressEscape()
  • pressCtrlS(), pressCtrlA(), pressDelete()
  • pressKey(int keyCode) - Press specific key
  • pressKeyCombo(int modifier, int key) - Key combinations

Control Flowโ€‹

  • stopChain() - Stop execution
  • stopIf(Predicate<ActionResult>) - Conditional stop
  • retry(ActionConfig, int) - Retry pattern
  • throwError(String) - Throw exception

Logging & Debuggingโ€‹

  • log(String) - Log message
  • ifFoundLog(String) / ifNotFoundLog(String) - Conditional logging
  • takeScreenshot(String) - Capture screenshot

Custom Handlersโ€‹

  • ifFoundDo(Consumer<ActionResult>) - Custom success handler
  • ifNotFoundDo(Consumer<ActionResult>) - Custom failure handler
  • ifFound(Consumer<ConditionalActionChain>) - Chain operations
  • ifNotFound(Consumer<ConditionalActionChain>) - Chain operations

Production Examplesโ€‹

Here's a complete, compilable example showing ConditionalActionChain in a real component:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.jspinak.brobot.action.Action;
import io.github.jspinak.brobot.action.ActionResult;
import io.github.jspinak.brobot.action.ObjectCollection;
import io.github.jspinak.brobot.action.conditionals.ConditionalActionChain;
import io.github.jspinak.brobot.datatypes.state.stateObject.stateImage.StateImage;

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

@Autowired
private Action action;

// Initialize StateImages (in real code, these would come from State definitions)
private final StateImage loginButton = new StateImage.Builder()
.addPattern("images/login/login-button.png")
.build();

private final StateImage usernameField = new StateImage.Builder()
.addPattern("images/login/username-field.png")
.build();

private final StateImage passwordField = new StateImage.Builder()
.addPattern("images/login/password-field.png")
.build();

private final StateImage submitButton = new StateImage.Builder()
.addPattern("images/login/submit-button.png")
.build();

private final StateImage successMessage = new StateImage.Builder()
.addPattern("images/login/success-message.png")
.build();

public ActionResult performLogin(String username, String password) {
return ConditionalActionChain.find(loginButton)
.ifFoundClick()
.ifNotFoundLog("Login button not visible")
.then(usernameField)
.ifFoundClick()
.ifFoundType(username)
.then(passwordField)
.ifFoundClick()
.ifFoundType(password)
.then(submitButton)
.ifFoundClick()
.then(successMessage)
.ifFoundLog("Login successful!")
.ifNotFoundLog("Login might have failed")
.perform(action, new ObjectCollection.Builder().build());
}
}

This example is fully compilable and demonstrates:

  • Complete Spring component setup with @Component and @Autowired
  • StateImage initialization with proper builders
  • ConditionalActionChain usage with real variables
  • Error handling and logging
  • Production-ready code structure

Best Practicesโ€‹

  1. Use then() for Sequential Actions: The then() method is essential for multi-step workflows
  2. No Explicit Waits: Use action configurations for timing, not wait() calls
  3. Leverage Convenience Methods: Use built-in methods like click() and type()
  4. Add Logging: Use ifFoundLog/ifNotFoundLog for debugging
  5. Handle Failures: Always provide ifNotFound alternatives
  6. Keep Chains Focused: Break complex workflows into smaller methods
  7. Think States, Not Processes: Focus on state transitions, not step-by-step procedures

Migration from Basic ConditionalActionChainโ€‹

The original ConditionalActionChain was limited. Here's how to migrate:

// OLD - Limited ConditionalActionChain (no then() method!)
ConditionalActionChain.find(button)
.ifFound(click())
// Can't continue to next element without then()!

// NEW - ConditionalActionChain
ConditionalActionChain.find(button)
.ifFoundClick()
.then(nextElement) // Now you can continue!
.ifFoundClick()
.then(anotherElement) // And continue further!
.ifFoundType("text")

Getting Startedโ€‹

Core ActionConfig Guidesโ€‹

Reference Documentationโ€‹

Testing Documentationโ€‹

Migration Resourcesโ€‹

Common Pitfalls to Avoidโ€‹

  1. Don't Use Explicit Waits: No wait() method - use action configurations for timing
  2. Don't Forget then(): Use then() to move between different elements
  3. Don't Mix APIs: Use ConditionalActionChain consistently
  4. Don't Ignore State: Think in terms of application states, not process steps
  5. Don't Skip Error Handling: Always handle the ifNotFound case