Lifecycle
@BeforeAll static void once() // 1x, must be static
@BeforeEach void setUp() // before EVERY test
@Test @DisplayName("...")
@AfterEach void tearDown() // runs even on failure
@AfterAll static void end() // 1x
@Disabled("reason") // visible skip > commented-out
Assertions
assertEquals(expected, actual); // THAT order
assertEquals(exp, act, "msg on failure");
assertTrue(x) assertFalse(x) assertNotNull(x)
assertAll(
() -> assertEquals("Products", title),
() -> assertEquals("1", cartCount)); // reports all
Data-driven
@ParameterizedTest(name = "{index}: {0} -> {2}")
@CsvSource({
"locked_out_user, secret_sauce, locked out",
"'', secret_sauce, Username is required"
}) // '' = empty string; quote values with commas
void invalidLogin(String u, String p, String err){...}
// needs junit-jupiter-params in pom.xml
Tags & subsets
@Tag("smoke") @Tag("regression")
mvn test "-Dgroups=smoke"
mvn test "-DexcludedGroups=slow"
// PowerShell: quote the whole -D flag
Screenshot on failure
@RegisterExtension TestWatcher w = new TestWatcher(){
public void testFailed(ExtensionContext c, Throwable t){
byte[] png = ((TakesScreenshot)driver)
.getScreenshotAs(OutputType.BYTES);
Files.write(Paths.get("screenshots", name + ".png"), png);
}}; // field, not static; sanitize the file name
Data sources beyond CsvSource
@ValueSource(strings = {"a", "b"}) one param
@CsvFileSource(resources = "/logins.csv",
numLinesToSkip = 1) real file
@MethodSource("myProvider") complex objects
// files live in src/test/resources
Run exactly what you need
mvn test -Dtest=LoginTest one class
mvn test -Dtest=LoginTest#validLogin one method
mvn test "-Dgroups=smoke" by tag
mvn test "-Dsurefire.rerunFailingTestsCount=1" # flaky triage
Reports
target/surefire-reports/ *.txt + TEST-*.xml
Tests run / Failures / Errors / Skipped
Failure -> app. Error -> test. First red block = story.