Custom annotations in Selenium Java - Build an Automation Script
package tests; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.time.Duration; // Custom annotation definition @Retention(RetentionPolicy.RUNTIME) @interface TestInfo { String testCaseId(); String description(); } // Page Object for Login Page class LoginPage { private WebDriver driver; private WebDriverWait wait; private By emailField = By.id("email"); private By passwordField = By.id("password"); private By loginButton = By.id("loginBtn"); public LoginPage(WebDriver driver) { this.driver = driver; this.wait = new WebDriverWait(driver, Duration.ofSeconds(10)); } public void enterEmail(String email) { WebElement emailInput = wait.until(ExpectedConditions.visibilityOfElementLocated(emailField)); emailInput.clear(); emailInput.sendKeys(email); } public void enterPassword(String password) { WebElement passwordInput = wait.until(ExpectedConditions.visibilityOfElementLocated(passwordField)); passwordInput.clear(); passwordInput.sendKeys(password); } public void clickLogin() { WebElement loginBtn = wait.until(ExpectedConditions.elementToBeClickable(loginButton)); loginBtn.click(); } } // Page Object for Dashboard Page class DashboardPage { private WebDriver driver; private WebDriverWait wait; private By welcomeMessage = By.id("welcomeMsg"); public DashboardPage(WebDriver driver) { this.driver = driver; this.wait = new WebDriverWait(driver, Duration.ofSeconds(10)); } public boolean isWelcomeMessageDisplayed() { return wait.until(ExpectedConditions.visibilityOfElementLocated(welcomeMessage)).isDisplayed(); } } public class LoginTest { private WebDriver driver; private LoginPage loginPage; private DashboardPage dashboardPage; @BeforeClass public void setUp() { driver = new ChromeDriver(); driver.manage().window().maximize(); loginPage = new LoginPage(driver); dashboardPage = new DashboardPage(driver); } @Test @TestInfo(testCaseId = "TC001", description = "Verify successful login redirects to dashboard") public void testSuccessfulLogin() { driver.get("https://example.com/login"); loginPage.enterEmail("user@example.com"); loginPage.enterPassword("Password123"); loginPage.clickLogin(); // Wait for URL to change to dashboard new WebDriverWait(driver, Duration.ofSeconds(10)) .until(ExpectedConditions.urlToBe("https://example.com/dashboard")); Assert.assertEquals(driver.getCurrentUrl(), "https://example.com/dashboard", "URL should be dashboard URL after login"); Assert.assertTrue(dashboardPage.isWelcomeMessageDisplayed(), "Welcome message should be visible on dashboard"); } @AfterClass public void tearDown() { if (driver != null) { driver.quit(); } } }
This test script uses Selenium WebDriver with TestNG to automate the login test.
We define a @TestInfo custom annotation to add metadata like test case ID and description to the test method. This helps organize and document tests clearly.
The Page Object Model is used to separate page elements and actions for the login and dashboard pages. This makes the code cleaner and easier to maintain.
Explicit waits ensure the test waits for elements to be visible or clickable before interacting, avoiding flaky tests.
The test method navigates to the login page, enters credentials, clicks login, then waits for the URL to change to the dashboard URL. It asserts the URL and checks that the welcome message is displayed.
Finally, the @AfterClass method closes the browser to clean up.
Now add data-driven testing with 3 different sets of login credentials using TestNG data provider
Practice
custom annotations in Selenium Java tests?Solution
Step 1: Understand what annotations do
Annotations add metadata to code elements like methods or classes, influencing how tests run or are processed.Step 2: Identify the role of custom annotations
Custom annotations let testers add their own metadata to control or describe tests beyond built-in annotations.Final Answer:
To add metadata that can control test behavior or provide extra information -> Option DQuick Check:
Custom annotations = metadata control [OK]
- Thinking custom annotations replace WebDriver methods
- Assuming they auto-generate reports without code
- Believing tests can run without Java code
TestInfo in Java?Solution
Step 1: Recall Java annotation syntax
Annotations use the keyword@interfaceand define methods without bodies for elements.Step 2: Check public @interface TestInfo { String author(); int priority() default 1; } syntax
public @interface TestInfo { String author(); int priority() default 1; } correctly usespublic @interface TestInfoand defines methods with optional default values.Final Answer:
public @interface TestInfo { String author(); int priority() default 1; } -> Option AQuick Check:
Annotation syntax = @interface + methods [OK]
- Using class instead of @interface
- Declaring fields instead of methods inside annotation
- Missing default keyword for optional elements
printTestInfo() is called?
import java.lang.reflect.Method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@interface TestInfo {
String author();
int priority() default 1;
}
public class SampleTest {
@TestInfo(author = "Alice", priority = 3)
public void testMethod() {}
public static void printTestInfo() throws Exception {
Method m = SampleTest.class.getMethod("testMethod");
TestInfo info = m.getAnnotation(TestInfo.class);
System.out.println(info.author() + ":" + info.priority());
}
}Solution
Step 1: Understand annotation retrieval via reflection
The methodgetAnnotation(TestInfo.class)fetches the annotation instance ontestMethod.Step 2: Check annotation values
The annotation hasauthor="Alice"andpriority=3, so printing them outputs "Alice:3".Final Answer:
Alice:3 -> Option CQuick Check:
Reflection reads annotation values correctly [OK]
- Assuming default priority is used instead of 3
- Expecting null because of missing retention policy
- Thinking reflection causes compile error
@interface TestInfo {
String author();
int priority() default 1;
}
And this usage:
@TestInfo(author = "Bob")
public void test() {}
Why might the following code throw a NullPointerException when accessing priority()?
TestInfo info = testMethod.getAnnotation(TestInfo.class); int p = info.priority();
Solution
Step 1: Check annotation retention policy
If the annotation lacks@Retention(RetentionPolicy.RUNTIME), it is not available at runtime, sogetAnnotationreturns null.Step 2: Understand NullPointerException cause
Sinceinfois null, callinginfo.priority()throws NullPointerException.Final Answer:
The annotation is not retained at runtime, so info is null -> Option BQuick Check:
Missing runtime retention = null annotation [OK]
- Assuming default values cause null
- Thinking missing author causes error
- Believing annotations can't have defaults
@StepInfo to mark test steps with a description and an optional severity level (default "LOW"). You also want to process these annotations at runtime to print all step descriptions with their severity. Which combination of annotation elements and retention policy is correct to achieve this?Solution
Step 1: Choose correct retention for runtime processing
To access annotations at runtime via reflection, use@Retention(RetentionPolicy.RUNTIME).Step 2: Define annotation elements with default values
Definedescription()as required andseverity()with default "LOW" for optional use.Final Answer:
@Retention(RetentionPolicy.RUNTIME) public @interface StepInfo { String description(); String severity() default "LOW"; } -> Option AQuick Check:
Runtime retention + default value = correct annotation [OK]
- Using CLASS or SOURCE retention prevents runtime access
- Omitting retention causes default CLASS retention
- Not providing default for optional elements
Ready to prove it?
Pass the test and this pattern moves unknown
