Unit Testing
Unit testing in Brobot ensures reproducible results by using static screenshots instead of live environments. This approach provides deterministic testing conditions where Find operations execute against known screen states while other actions are mocked.
Overviewโ
Brobot unit testing combines:
- Centralized mock mode management via
MockModeManager - Real Find operations on static screenshots
- Mocked actions (click, drag, type, etc.) for safety and speed
- Deterministic results for reliable test assertions
Base Test Classโ
All Brobot unit tests should extend BrobotTestBase for automatic mock mode configuration:
import io.github.jspinak.brobot.test.BrobotTestBase;
import org.junit.jupiter.api.Test;
public class MyUnitTest extends BrobotTestBase {
@Test
public void testFeature() {
// Mock mode is automatically enabled
// Test runs safely in headless environments
}
}
BrobotTestBase automatically:
- Enables mock mode via
MockModeManager - Configures fast mock timings (0.01-0.04s)
- Ensures headless compatibility
- Prevents AWTException errors
Configurationโ
Automatic Mock Mode Configurationโ
When extending BrobotTestBase, mock mode is automatically configured via MockModeManager:
import io.github.jspinak.brobot.test.BrobotTestBase;
import io.github.jspinak.brobot.config.mock.MockModeManager;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class LoginTest extends BrobotTestBase {
// No manual mock configuration needed!
@Test
public void testLogin() {
// Mock mode is already enabled
assertTrue(MockModeManager.isMockMode());
}
}
Manual Configuration via Propertiesโ
If not using BrobotTestBase, configure through application.properties:
# Enable mock mode for unit testing
brobot.mock=true
# Screenshot configuration
brobot.screenshot.path=screenshots/
brobot.screenshot.filename=screen
# Testing settings
brobot.testing.iteration=1
brobot.testing.send-logs=true
Or using YAML:
brobot:
mock: true
screenshot:
path: screenshots/
filename: screen
testing:
iteration: 1
send-logs: true
Test Configurationโ
For Spring Boot tests, extend BrobotTestBase and use property configuration:
@SpringBootTest
@TestPropertySource(properties = {
"brobot.screenshot.path=src/test/resources/screenshots/"
})
class UnitTest extends BrobotTestBase {
// Mock mode is automatically enabled by BrobotTestBase
// Additional properties are handled by Spring
}
For non-Spring tests:
public class SimpleUnitTest extends BrobotTestBase {
@BeforeEach
@Override
public void setupTest() {
super.setupTest(); // Enables mock mode via MockModeManager
// Add any additional setup
}
}```
## Test Structure
### Basic Unit Test Example
```java
import io.github.jspinak.brobot.test.BrobotTestBase;
import io.github.jspinak.brobot.actions.actionExecution.Action;
import io.github.jspinak.brobot.actions.methods.basicactions.find.PatternFindOptions;
import io.github.jspinak.brobot.actions.methods.basicactions.find.ClickOptions;
import io.github.jspinak.brobot.actions.methods.basicactions.find.TypeOptions;
import io.github.jspinak.brobot.datatypes.state.stateObject.stateImage.StateImage;
import io.github.jspinak.brobot.datatypes.state.ObjectCollection;
import io.github.jspinak.brobot.reports.ActionResult;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
@TestPropertySource(properties = {
"brobot.screenshot.test-path=src/test/resources/screenshots/"
})
class LoginAutomationTest extends BrobotTestBase {
@Autowired
private Action action;
// Mock mode is automatically enabled by BrobotTestBase
@Test
void testSuccessfulLogin() {
// Arrange - Create state objects
StateImage usernameField = new StateImage.Builder()
.addPattern("username_field")
.build();
StateImage passwordField = new StateImage.Builder()
.addPattern("password_field")
.build();
StateImage loginButton = new StateImage.Builder()
.addPattern("login_button")
.build();
// Act - Perform actions
// Find and click username field
PatternFindOptions findOptions = new PatternFindOptions.Builder()
.setStrategy(PatternFindOptions.Strategy.BEST)
.build();
ActionResult usernameResult = action.perform(findOptions, usernameField);
// Type username
TypeOptions typeOptions = new TypeOptions.Builder()
.setTypeDelay(0.05)
.build();
action.perform(typeOptions, new ObjectCollection.Builder()
.withStrings("testuser")
.build());
// Find and click password field
ActionResult passwordResult = action.perform(findOptions, passwordField);
action.perform(typeOptions, new ObjectCollection.Builder()
.withStrings("testpass")
.build());
// Click login button
ClickOptions clickOptions = new ClickOptions.Builder()
.build(); // LEFT button is default
ActionResult loginResult = action.perform(clickOptions, loginButton);
// Assert
assertTrue(usernameResult.isSuccess());
assertTrue(passwordResult.isSuccess());
assertTrue(loginResult.isSuccess());
assertEquals(1, loginResult.size());
assertThat(loginResult.getBestMatch()).isPresent();
}
}
Testing with Multiple Screenshotsโ
import io.github.jspinak.brobot.actions.actionExecution.Action;
import io.github.jspinak.brobot.actions.methods.basicactions.find.PatternFindOptions;
import io.github.jspinak.brobot.actions.methods.basicactions.find.ClickOptions;
import io.github.jspinak.brobot.datatypes.state.stateObject.stateImage.StateImage;
import io.github.jspinak.brobot.reports.ActionResult;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Test
void testNavigationFlow() {
// Screenshots configured via properties file
// brobot.screenshot.path=src/test/resources/screenshots/
// Place files: step1_login.png, step2_dashboard.png, step3_settings.png
// Create find options for navigation
PatternFindOptions findOptions = new PatternFindOptions.Builder()
.setStrategy(PatternFindOptions.Strategy.BEST)
.setSimilarity(0.8)
.build();
// Test navigation sequence
// Step 1: Login
StateImage loginButton = new StateImage.Builder()
.addPattern("login_button.png")
.build();
ActionResult loginResult = action.perform(findOptions, loginButton);
action.perform(new ClickOptions.Builder().build(), loginButton);
// Step 2: Navigate to dashboard
StateImage dashboardLink = new StateImage.Builder()
.addPattern("dashboard_link")
.build();
ActionResult dashboardResult = action.perform(findOptions, dashboardLink);
action.perform(new ClickOptions.Builder().build(), dashboardLink);
// Step 3: Open settings
StateImage settingsIcon = new StateImage.Builder()
.addPattern("settings_icon.png")
.build();
ActionResult settingsResult = action.perform(findOptions, settingsIcon);
action.perform(new ClickOptions.Builder().build(), settingsIcon);
// Verify each step
assertTrue(loginResult.isSuccess());
assertTrue(dashboardResult.isSuccess());
assertTrue(settingsResult.isSuccess());
}
Working with ActionResultโ
ActionResult is returned by action executions and provides comprehensive information about matches found:
import io.github.jspinak.brobot.actions.actionExecution.Action;
import io.github.jspinak.brobot.actions.methods.basicactions.find.PatternFindOptions;
import io.github.jspinak.brobot.datatypes.state.stateObject.stateImage.StateImage;
import io.github.jspinak.brobot.datatypes.primitives.match.Match;
import io.github.jspinak.brobot.datatypes.primitives.region.Region;
import io.github.jspinak.brobot.reports.ActionResult;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Optional;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.assertThat;
@Test
void testFindOperations() {
// Create find configuration
PatternFindOptions findOptions = new PatternFindOptions.Builder()
.setStrategy(PatternFindOptions.Strategy.ALL)
.setSimilarity(0.7)
.build();
// Perform find action
StateImage submitButton = new StateImage.Builder()
.addPattern("submit_button.png")
.build();
ActionResult result = action.perform(findOptions, submitButton);
// Test result properties
assertFalse(result.isEmpty());
assertEquals(2, result.size());
// Access best match
Optional<Match> bestMatch = result.getBestMatch();
assertTrue(bestMatch.isPresent());
assertTrue(bestMatch.get().getScore() > 0.8);
// Test specific regions
List<Region> regions = result.getMatchRegions();
assertThat(regions).hasSize(2);
// Test filtering
ActionResult highScoreMatches = new ActionResult();
result.getMatchList().stream()
.filter(match -> match.getScore() > 0.9)
.forEach(highScoreMatches::add);
assertTrue(highScoreMatches.size() > 0);
}
Mock Behavior Verificationโ
import io.github.jspinak.brobot.actions.actionExecution.Action;
import io.github.jspinak.brobot.actions.methods.basicactions.find.PatternFindOptions;
import io.github.jspinak.brobot.actions.methods.basicactions.find.ClickOptions;
import io.github.jspinak.brobot.datatypes.state.stateObject.stateImage.StateImage;
import io.github.jspinak.brobot.reports.ActionResult;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestPropertySource;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Test
@TestPropertySource(properties = {
"brobot.mock.time-click=0.1",
"brobot.mock.time-find-first=0.2"
})
void testMockTimings() {
long startTime = System.currentTimeMillis();
// Perform mocked actions
PatternFindOptions findOptions = new PatternFindOptions.Builder()
.setStrategy(PatternFindOptions.Strategy.FIRST)
.build();
StateImage button = new StateImage.Builder()
.addPattern("button.png")
.build();
ActionResult findResult = action.perform(findOptions, button);
ClickOptions clickOptions = new ClickOptions.Builder().build();
ActionResult clickResult = action.perform(clickOptions, button);
long duration = System.currentTimeMillis() - startTime;
// Verify mock timing (should be approximately 300ms)
assertTrue(duration >= 250 && duration <= 350);
}
Advanced Testing Patternsโ
Pattern-Based Testingโ
import io.github.jspinak.brobot.actions.actionExecution.Action;
import io.github.jspinak.brobot.actions.methods.basicactions.find.PatternFindOptions;
import io.github.jspinak.brobot.datatypes.state.stateObject.stateImage.StateImage;
import io.github.jspinak.brobot.reports.ActionResult;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Test
void testPatternMatching() {
// Test with different similarity thresholds
PatternFindOptions strictFind = new PatternFindOptions.Builder()
.setStrategy(PatternFindOptions.Strategy.BEST)
.setSimilarity(0.95)
.build();
PatternFindOptions relaxedFind = new PatternFindOptions.Builder()
.setStrategy(PatternFindOptions.Strategy.ALL)
.setSimilarity(0.70)
.build();
StateImage targetPattern = new StateImage.Builder()
.addPattern("target_button.png")
.build();
// Test strict matching
ActionResult strictResult = action.perform(strictFind, targetPattern);
assertTrue(strictResult.size() <= 1, "Strict matching should find at most one match");
// Test relaxed matching
ActionResult relaxedResult = action.perform(relaxedFind, targetPattern);
assertTrue(relaxedResult.size() >= strictResult.size(),
"Relaxed matching should find at least as many matches");
}
Custom Assertionsโ
import io.github.jspinak.brobot.reports.ActionResult;
import io.github.jspinak.brobot.datatypes.primitives.region.Region;
import io.github.jspinak.brobot.datatypes.primitives.match.Match;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class BrobotAssertions {
public static void assertFoundInRegion(ActionResult result, Region expectedRegion) {
assertTrue(result.isSuccess(), "Expected to find matches");
boolean foundInRegion = result.getMatchList().stream()
.anyMatch(match -> expectedRegion.contains(match.getRegion()));
assertTrue(foundInRegion, "No matches found in expected region");
}
public static void assertMinimumScore(ActionResult result, double minScore) {
assertTrue(result.getBestMatch().isPresent(), "No matches found");
assertTrue(
result.getBestMatch().get().getScore() >= minScore,
String.format("Best match score %.3f below minimum %.3f",
result.getBestMatch().get().getScore(), minScore)
);
}
}
API Migration Notesโ
Important: Brobot 1.1.0+ uses modern API patterns. When migrating from Brobot 1.0.x:
ActionOptions โ ActionConfig Migrationโ
- Use
PatternFindOptions,ClickOptions,TypeOptions, etc. instead of legacyActionOptions - Configure via properties files (
brobot.mock=true) rather than programmatic setup - ActionConfig classes provide better type safety and clearer separation of concerns
ActionRecord for Test Historyโ
ActionRecordis the class for recording action history and mock data- Use
ActionRecordfor creating test snapshots and mock responses - ActionRecord replaced the legacy MatchSnapshot class from Brobot 1.0.x
Modern Property Namesโ
- Use
brobot.mock=true(NOTbrobot.core.mockorbrobot.mock.mode) - Use
brobot.screenshot.pathfor screenshot configuration - Use
brobot.mock.time-*properties for mock timing configuration
See Upgrading to Latest for complete migration guide.
Best Practicesโ
-
Modern API Usage
- Use ActionConfig subclasses (PatternFindOptions, ClickOptions) instead of ActionOptions
- Configure tests through properties files
- Leverage action chaining with
.then()for complex test scenarios
-
Screenshot Management
- Use descriptive filenames (e.g.,
login_page_chrome.png) - Keep screenshots in
src/test/resources/screenshots/ - Version control your test screenshots
- Use descriptive filenames (e.g.,
-
Test Isolation
- Configure screenshots via properties rather than programmatic setup
- Use
@TestPropertySourcefor test-specific settings - Avoid manual configuration in
@BeforeEachmethods
-
Assertions
- Test both positive and negative cases
- Verify match scores and regions using ActionResult methods
- Use custom assertion methods for common patterns
-
Mock Configuration
- Set realistic mock timings via properties
- Test timeout scenarios
- Verify mock behavior in CI environments
Troubleshootingโ
- No matches found: Verify screenshot path configuration in properties
- Unexpected results: Check mock mode is enabled (
brobot.mock=true) - Slow tests: Adjust mock timings in properties for faster execution
- Flaky tests: Ensure screenshots represent stable UI states
- API conflicts: Use ActionConfig classes instead of deprecated ActionOptions
Related Documentationโ
Essential Testing Guidesโ
- Testing Introduction - Overview of all Brobot testing approaches
- Integration Testing - Spring Boot integration testing patterns
- Profile-Based Testing - Test profiles and configuration isolation
- Test Utilities - BrobotTestBase and testing helper classes
- Testing Strategy - Comprehensive testing strategy and best practices
Mock Mode & Testingโ
- Mock Mode Guide - Comprehensive mock testing framework
- Mock Mode Manager - Centralized mock configuration
- Mock Stochasticity - Probabilistic testing patterns
- ActionHistory Mock Snapshots - Creating mock test data
- Action Recording - Recording actions for test creation
ActionConfig Systemโ
- ActionConfig Overview - ActionConfig system introduction
- ActionConfig Examples - Practical testing examples
- ActionConfig Reference - Complete API reference
- Upgrading to Latest - Migration guide from 1.0.x
Configuration & Setupโ
- BrobotProperties Usage - Configuration guide
- Properties Reference - Complete properties documentation
- Headless Configuration - Headless environment setup
- Auto Configuration - Spring Boot auto-configuration
Advanced Testingโ
- Enhanced Mock Testing System - Advanced mock scenarios
- Mat Testing Utilities - OpenCV Mat testing utilities
- Debugging Pattern Matching - Troubleshooting pattern matching
- CI/CD Testing - Continuous integration patterns