Another option that may work is applying JUnit Parameterization to your test. It is my present understanding that the Parameters for the implementation are always executed in the order they are provided.
Using that concept you could have your JUnit implementation accept the URL as a constructor argument and fork the test internally based on the parameters provided.
In order to ensure that you're using the same WebDriver reference it would probably need to be static @BeforeClass/@AfterClass declarations. With that you may be able to have the parameters chain off one another, effectively testing that "From the previous test I am on page X. While here I will perform task Y. At the end of this test I will be on page Z, or in state A".
In Unit-level testing I would certainly say this solution would be bad form, but when you integrate a tool like Selenium you start acting on the integration-test level. I'm fairly new to this concept myself, but in the integration-test level it seems the rules of modularity are a bit more fuzzy since you will have conditions that are dependent.
I was curious, so I tried it out. It acts like I was thinking it would, if we assume we can treat the application as a static resource in relation to the test.
package demo.testing;
import java.util.List;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;
@RunWith(Parameterized.class)
public class SequentialParams {
private static SystemState state;
@BeforeClass
public static void validateBeforeState() {
state = new SystemState();
Assert.assertFalse(state.one);
Assert.assertFalse(state.two);
Assert.assertFalse(state.three);
Assert.assertFalse(state.four);
}
@Parameters
public static Object buildParameters() {
Runnable reset = new Runnable() {
public void run() {
state.one = false;
state.two = false;
state.three = false;
state.four = false;
}
};
Runnable oneToTrue = new Runnable() {
public void run() {
state.one = true;
}
};
Runnable twoToTrue = new Runnable() {
public void run() {
state.two = true;
}
};
Runnable threeToTrue = new Runnable() {
public void run() {
state.three = true;
}
};
Runnable fourToTrue = new Runnable() {
public void run() {
state.four = true;
}
};
Predicate<SystemState> oneIsTrue = new Predicate<SequentialParams.SystemState>() {
public boolean apply(SystemState input) {
return input.one;
}
};
Predicate<SystemState> twoIsTrue = new Predicate<SequentialParams.SystemState>() {
public boolean apply(SystemState input) {
return input.two;
}
};
Predicate<SystemState> threeIsTrue = new Predicate<SequentialParams.SystemState>() {
public boolean apply(SystemState input) {
return input.three;
}
};
Predicate<SystemState> fourIsTrue = new Predicate<SequentialParams.SystemState>() {
public boolean apply(SystemState input) {
return input.four;
}
};
Predicate<SystemState> oneIsFalse = new Predicate<SequentialParams.SystemState>() {
public boolean apply(SystemState input) {
return !input.one;
}
};
Predicate<SystemState> twoIsFalse = new Predicate<SequentialParams.SystemState>() {
public boolean apply(SystemState input) {
return !input.two;
}
};
Predicate<SystemState> threeIsFalse = new Predicate<SequentialParams.SystemState>() {
public boolean apply(SystemState input) {
return !input.three;
}
};
Predicate<SystemState> fourIsFalse = new Predicate<SequentialParams.SystemState>() {
public boolean apply(SystemState input) {
return !input.four;
}
};
List<Object[]> params = Lists.newArrayList();
params.add(new Object[]{Predicates.and(oneIsFalse, twoIsFalse, threeIsFalse, fourIsFalse), oneToTrue, Predicates.and(oneIsTrue, twoIsFalse, threeIsFalse, fourIsFalse)});
params.add(new Object[]{Predicates.and(oneIsTrue, twoIsFalse, threeIsFalse, fourIsFalse), twoToTrue, Predicates.and(oneIsTrue, twoIsTrue, threeIsFalse, fourIsFalse)});
params.add(new Object[]{Predicates.and(oneIsTrue, twoIsTrue, threeIsFalse, fourIsFalse), threeToTrue, Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsFalse)});
params.add(new Object[]{Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsFalse), fourToTrue, Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsTrue)});
params.add(new Object[]{ Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsTrue), reset, Predicates.and(oneIsFalse, twoIsFalse, threeIsFalse, fourIsFalse)});
params.add(new Object[]{Predicates.and(oneIsFalse, twoIsFalse, threeIsFalse, fourIsFalse), threeToTrue, Predicates.and(oneIsFalse, twoIsFalse, threeIsTrue, fourIsFalse)});
params.add(new Object[]{Predicates.and(oneIsFalse, twoIsFalse, threeIsTrue, fourIsFalse), oneToTrue, Predicates.and(oneIsTrue, twoIsFalse, threeIsTrue, fourIsFalse)});
params.add(new Object[]{Predicates.and(oneIsTrue, twoIsFalse, threeIsTrue, fourIsFalse), fourToTrue, Predicates.and(oneIsTrue, twoIsFalse, threeIsTrue, fourIsTrue)});
params.add(new Object[]{Predicates.and(oneIsTrue, twoIsFalse, threeIsTrue, fourIsTrue), twoToTrue, Predicates.and(oneIsTrue, twoIsTrue, threeIsTrue, fourIsTrue)});
return params;
}
Predicate<SystemState> verifyStartState;
Runnable changeState;
Predicate<SystemState> verifyEndState;
public SequentialParams(Predicate<SystemState> pre, Runnable task, Predicate<SystemState> post) {
verifyStartState = pre;
changeState = task;
verifyEndState = post;
}
@Test
public void perform() {
Assert.assertTrue(verifyStartState.apply(state));
changeState.run();
Assert.assertTrue(verifyEndState.apply(state));
}
private static class SystemState {
public boolean one = false;
public boolean two = false;
public boolean three = false;
public boolean four = false;
}
}