Transitions
Version Note
This tutorial has been updated for Brobot 1.2.0+ with the new @TransitionSet annotation system.
Transitions define how to navigate between states. With the new @TransitionSet annotation system, all transitions for a state are grouped together in a single class, providing better organization and clearer intent.
Modern Approach: Unified Transition Classes
HomepageTransitions.java
// Note: BrobotProperties must be injected as a dependency
@Autowired
private BrobotProperties brobotProperties;
package com.example.mrdoob.transitions;
import org.springframework.stereotype.Component;
import com.example.mrdoob.states.Homepage;
import com.example.mrdoob.states.Harmony;
import io.github.jspinak.brobot.action.Action;
import io.github.jspinak.brobot.annotations.FromTransition;
import io.github.jspinak.brobot.annotations.IncomingTransition;
import io.github.jspinak.brobot.annotations.TransitionSet;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* All transitions for the Homepage state.
* Contains FromTransitions from other states TO Homepage,
* and a IncomingTransition to verify arrival at Homepage.
*/
@TransitionSet(state = Homepage.class, description = "MrDoob Homepage transitions")
@Component
@RequiredArgsConstructor
@Slf4j
public class HomepageTransitions {
private final Homepage homepage;
private final Harmony harmony;
private final Action action;
/**
* Navigate from Harmony back to Homepage.
* This might involve clicking a home/back button.
*/
@FromTransition(from = Harmony.class, priority = 1, description = "Return from Harmony to Homepage")
public boolean fromHarmony() {
log.info("Navigating from Harmony to Homepage");
// Mock mode support for testing
if (io.github.jspinak.brobot.config.core.brobotProperties.getCore().isMock()) {
log.info("Mock mode: simulating successful navigation");
return true;
}
// Assuming there's a home button or back navigation in Harmony
return action.click(harmony.getHomeButton()).isSuccess();
}
/**
* Verify that we have successfully arrived at the Homepage state.
* Checks for the presence of homepage-specific elements.
*/
@IncomingTransition(description = "Verify arrival at Homepage", required = true)
public boolean verifyArrival() {
log.info("Verifying arrival at Homepage");
// Mock mode support for testing
if (io.github.jspinak.brobot.config.core.brobotProperties.getCore().isMock()) {
log.info("Mock mode: simulating successful verification");
return true;
}
// Check for presence of homepage-specific elements
boolean foundHarmonyLink = action.find(homepage.getHarmony()).isSuccess();
if (foundHarmonyLink) {
log.info("Successfully confirmed Homepage is active");
return true;
} else {
log.error("Failed to confirm Homepage - harmony link not found");
return false;
}
}
}
HarmonyTransitions.java
package com.example.mrdoob.transitions;
import org.springframework.stereotype.Component;
import com.example.mrdoob.states.Homepage;
import com.example.mrdoob.states.Harmony;
import com.example.mrdoob.states.About;
import io.github.jspinak.brobot.action.Action;
import io.github.jspinak.brobot.annotations.FromTransition;
import io.github.jspinak.brobot.annotations.IncomingTransition;
import io.github.jspinak.brobot.annotations.TransitionSet;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* All transitions for the Harmony state.
* Contains FromTransitions from other states TO Harmony,
* and a IncomingTransition to verify arrival at Harmony.
*/
@TransitionSet(state = Harmony.class, description = "MrDoob Harmony page transitions")
@Component
@RequiredArgsConstructor
@Slf4j
public class HarmonyTransitions {
private final Homepage homepage;
private final Harmony harmony;
private final About about;
private final Action action;
/**
* Navigate from Homepage to Harmony by clicking the harmony link.
*/
@FromTransition(from = Homepage.class, priority = 1, description = "Navigate from Homepage to Harmony")
public boolean fromHomepage() {
log.info("Navigating from Homepage to Harmony");
// Mock mode support for testing
if (io.github.jspinak.brobot.config.core.brobotProperties.getCore().isMock()) {
log.info("Mock mode: simulating successful navigation");
return true;
}
return action.click(homepage.getHarmony()).isSuccess();
}
/**
* Navigate from About page back to Harmony.
*/
@FromTransition(from = About.class, priority = 2, description = "Return from About to Harmony")
public boolean fromAbout() {
log.info("Navigating from About to Harmony");
// Mock mode support for testing
if (io.github.jspinak.brobot.config.core.brobotProperties.getCore().isMock()) {
log.info("Mock mode: simulating successful navigation");
return true;
}
// Assuming there's a back button or harmony link in About page
return action.click(about.getBackButton()).isSuccess();
}
/**
* Verify that we have successfully arrived at the Harmony state.
* Checks for the presence of harmony-specific elements.
*/
@IncomingTransition(description = "Verify arrival at Harmony", required = true)
public boolean verifyArrival() {
log.info("Verifying arrival at Harmony");
// Mock mode support for testing
if (io.github.jspinak.brobot.config.core.brobotProperties.getCore().isMock()) {
log.info("Mock mode: simulating successful verification");
return true;
}
// Check for presence of harmony-specific elements
boolean foundAboutLink = action.find(harmony.getAbout()).isSuccess();
boolean foundCanvas = action.find(harmony.getHarmonyCanvas()).isSuccess();
if (foundAboutLink || foundCanvas) {
log.info("Successfully confirmed Harmony state is active");
return true;
} else {
log.error("Failed to confirm Harmony state - expected elements not found");
return false;
}
}
}
AboutTransitions.java
package com.example.mrdoob.transitions;
import org.springframework.stereotype.Component;
import com.example.mrdoob.states.Harmony;
import com.example.mrdoob.states.About;
import io.github.jspinak.brobot.action.Action;
import io.github.jspinak.brobot.annotations.FromTransition;
import io.github.jspinak.brobot.annotations.IncomingTransition;
import io.github.jspinak.brobot.annotations.TransitionSet;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* All transitions for the About state.
* Contains FromTransitions from other states TO About,
* and a IncomingTransition to verify arrival at About.
*/
@TransitionSet(state = About.class, description = "MrDoob About page transitions")
@Component
@RequiredArgsConstructor
@Slf4j
public class AboutTransitions {
private final Harmony harmony;
private final About about;
private final Action action;
/**
* Navigate from Harmony to About by clicking the about link.
*/
@FromTransition(from = Harmony.class, priority = 1, description = "Navigate from Harmony to About")
public boolean fromHarmony() {
log.info("Navigating from Harmony to About");
// Mock mode support for testing
if (io.github.jspinak.brobot.config.core.brobotProperties.getCore().isMock()) {
log.info("Mock mode: simulating successful navigation");
return true;
}
return action.click(harmony.getAbout()).isSuccess();
}
/**
* Verify that we have successfully arrived at the About state.
* Checks for the presence of about-specific elements.
*/
@IncomingTransition(description = "Verify arrival at About", required = true)
public boolean verifyArrival() {
log.info("Verifying arrival at About");
// Mock mode support for testing
if (io.github.jspinak.brobot.config.core.brobotProperties.getCore().isMock()) {
log.info("Mock mode: simulating successful verification");
return true;
}
// Check for presence of about-specific elements
boolean foundAboutContent = action.find(about.getAboutContent()).isSuccess();
if (foundAboutContent) {
log.info("Successfully confirmed About state is active");
return true;
} else {
log.error("Failed to confirm About state - content not found");
return false;
}
}
}
Key Improvements with @TransitionSet
Comparison: Old vs New
Before (Individual Transition Classes - v1.1.0)
// Separate file for each transition
@Transition(from = Homepage.class, to = Harmony.class)
@Component
public class HomepageToHarmonyTransition {
private final Action action;
private final Homepage homepage;
public boolean execute() {
return action.click(homepage.getHarmony()).isSuccess();
}
}
// Another separate file
@Transition(from = Harmony.class, to = About.class)
@Component
public class HarmonyToAboutTransition {
private final Action action;
private final Harmony harmony;
public boolean execute() {
return action.click(harmony.getAbout()).isSuccess();
}
}
After (Unified Transition Classes - v1.2.0+)
// All transitions for Harmony in ONE class
@TransitionSet(state = Harmony.class)
@Component
public class HarmonyTransitions {
@FromTransition(from = Homepage.class, priority = 1)
public boolean fromHomepage() {
if (brobotProperties.getCore().isMock()) return true;
return action.click(homepage.getHarmony()).isSuccess();
}
@FromTransition(from = About.class, priority = 2)
public boolean fromAbout() {
if (brobotProperties.getCore().isMock()) return true;
return action.click(about.getBackButton()).isSuccess();
}
@IncomingTransition(required = true)
public boolean verifyArrival() {
if (brobotProperties.getCore().isMock()) return true;
return action.find(harmony.getAboutLink()).isSuccess();
}
}
File Organization
Organize your transitions alongside states:
src/main/java/com/example/mrdoob/
├── states/
│ ├── Homepage.java
│ ├── Harmony.java
│ └── About.java
└── transitions/
├── HomepageTransitions.java # All transitions for Homepage
├── HarmonyTransitions.java # All transitions for Harmony
└── AboutTransitions.java # All transitions for About
Benefits of the New Approach
- Better Organization: All transitions for a state in ONE place
- Clearer Intent: FromTransitions vs IncomingTransition makes navigation flow obvious
- Less Boilerplate: No need for separate classes for each transition path
- Mock Mode Ready: Easy to add testing support with framework settings check
- Natural Structure: File organization mirrors state structure
- Easier Maintenance: Adding new transitions is straightforward
Testing Transitions
The unified structure makes testing easier:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {TestConfiguration.class})
public class HarmonyTransitionsTest {
@Autowired
private HarmonyTransitions harmonyTransitions;
@MockBean
private Action action;
@MockBean
private Homepage homepage;
@Test
public void testFromHomepageTransition() {
// Given
when(action.click(any())).thenReturn(
new ActionResult.Builder().setSuccess(true).build()
);
// When
boolean result = harmonyTransitions.fromHomepage();
// Then
assertTrue(result);
verify(action).click(homepage.getHarmony());
}
@Test
public void testVerifyArrival() {
// Given
when(action.find(any())).thenReturn(
new ActionResult.Builder().setSuccess(true).build()
);
// When
boolean arrived = harmonyTransitions.verifyArrival();
// Then
assertTrue(arrived);
}
}
Best Practices
- Always include mock mode support for testing environments
- Use descriptive method names like
fromHomepage()
,fromAbout()
- Add comprehensive logging for debugging
- Verify critical elements in IncomingTransition methods
- Handle failures gracefully with try-catch blocks where appropriate
- Set appropriate priorities when multiple paths exist to the same state
Next Steps
With states and transitions defined using the @TransitionSet system, your entire MrDoob automation is ready to run. The framework handles all registration and wiring automatically - you just focus on the automation logic!