Selenium with Java: From Zero to Real Tests
You already know Java. This guide teaches only what is new: Selenium words, objects and methods, and how to use them to test a real web page. Every code example is Java and was run successfully.
Important
Red boxes and rows: learn these first. They are used in almost every test and asked in almost every interview.
Good to know
Blue boxes and rows: useful, but you can learn them later or only when needed.
1. What is Selenium and how does it work?
Selenium is a Java library (a set of classes in a jar file) that controls a real browser. Your Java code says "open this page", "type here", "click that button", and the browser does it. Then your code checks the result.
Selenium cannot talk to Chrome directly. A small program called the browser driver sits in the middle and translates the commands. In Selenium 4, a helper called Selenium Manager downloads the correct driver for you automatically.
Words you will hear
| Word | Simple meaning |
|---|---|
| WebDriver | The main Java interface. One WebDriver object = one browser you can control. |
| Driver / ChromeDriver | WebDriver is the interface. ChromeDriver is the class that opens Chrome. FirefoxDriver and EdgeDriver do the same for other browsers. |
| WebElement | One thing on the page: a button, text box, link, label. Your code holds it in a WebElement variable. |
| Locator (By) | The way to tell Selenium which element you mean: by id, name, CSS, XPath and so on. |
| Session | From new ChromeDriver() to driver.quit(). One browser window lifetime. |
| DOM | The page as a tree of elements. Locators search this tree. |
| Headless | Running the browser with no visible window. Faster, used on servers. |
| Selenium Grid | Runs tests on many machines and browsers at once. Good to know only. |
| Selenium IDE | A browser plugin that records clicks. Not used in real projects. |
2. Setup in Java (Maven)
ImportantDependencies
Create a Maven project, then add these dependencies to pom.xml. Maven downloads the jars for you.
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.27.0</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.10.2</version>
</dependency>What it does: selenium-java is Selenium itself. testng is the test framework that runs your tests and gives you Assert. Use a recent Selenium version if you see a newer one on Maven Central. A "CDP version" warning in the console is harmless.
Also install Google Chrome. Nothing else is needed, Selenium Manager gets the driver.
Your first test (complete)
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
public class FirstTest {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver(); // 1. open Chrome
driver.get("https://example.com"); // 2. go to a page
String title = driver.getTitle(); // 3. read the page title
System.out.println(title);
driver.findElement(By.tagName("a")).click(); // 4. find a link and click it
driver.quit(); // 5. close the browser
}
}What it does: Five steps that every Selenium test has: open the browser, go to a page, find elements and act on them, check something, close the browser.
ImportantWhy WebDriver on the left side?
WebDriver driver = new ChromeDriver(); The variable type is the interface WebDriver, the object is ChromeDriver. This is Java polymorphism. To run on Firefox you only change the right side to new FirefoxDriver(). Everything else stays the same.
ImportantNever forget quit()
Always call driver.quit(). If you forget it, browser windows stay open and eat memory. In a test class, put it in @AfterMethod so it runs even when the test fails.
3. All the main classes in one picture
| Class / interface | Use |
|---|---|
WebDriver | Controls the browser: open pages, find elements, switch windows. |
WebElement | One element: click it, type in it, read its text. |
By | Describes how to find an element (id, CSS, XPath...). |
Select | Helper for <select> dropdowns. |
Actions | Mouse and keyboard actions: hover, double click, drag and drop. |
Alert | Browser popups (alert, confirm, prompt). |
WebDriverWait + ExpectedConditions | Wait until something is true. |
JavascriptExecutor | Run JavaScript inside the page. |
Keys | Special keys: ENTER, TAB, BACK_SPACE... |
ChromeOptions | Settings for Chrome: headless, window size, notifications off. |
4. WebDriver methods (the browser)
These are called on the driver object. Red rows you will use daily.
| Method | What it does | Example | |
|---|---|---|---|
| Important | get(String url) | Open a web page and wait until it loads. | driver.get("https://shop.com"); |
| Important | getTitle() | Returns the page title. | String t = driver.getTitle(); |
| Important | getCurrentUrl() | Returns the current address. | driver.getCurrentUrl(); |
| Important | findElement(By) | Finds the first matching element. Throws NoSuchElementException if none. | driver.findElement(By.id("email")); |
| Important | findElements(By) | Finds all matching elements as a List. Empty list if none (no error). | List<WebElement> rows = driver.findElements(By.tagName("tr")); |
| Important | quit() | Closes every window and ends the session. | driver.quit(); |
| Important | close() | Closes only the current window or tab. | driver.close(); |
| Important | navigate().back() / forward() / refresh() / to(url) | Browser back, forward, reload, go to a URL. | driver.navigate().back(); |
| Important | manage().window().maximize() | Makes the window full size. | driver.manage().window().maximize(); |
| Important | manage().timeouts().implicitlyWait(Duration) | Global wait for finding elements (see waits section). | driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5)); |
| Important | switchTo().frame(...) | Go inside an iframe. Use name, id, index or WebElement. | driver.switchTo().frame("payFrame"); |
| Important | switchTo().defaultContent() | Come back from a frame to the main page. | driver.switchTo().defaultContent(); |
| Important | switchTo().window(String handle) | Move to another window or tab. | driver.switchTo().window(handle); |
| Important | switchTo().alert() | Take control of a browser popup. | Alert a = driver.switchTo().alert(); |
| Important | getWindowHandle() / getWindowHandles() | Id of the current window / ids of all open windows (a Set<String>). | Set<String> all = driver.getWindowHandles(); |
| Good to know | getPageSource() | The full HTML of the page as a String. | driver.getPageSource(); |
| Good to know | switchTo().newWindow(WindowType.TAB) | Open a new tab and move to it. | driver.switchTo().newWindow(WindowType.TAB); |
| Good to know | switchTo().parentFrame() | Go up one frame level. | driver.switchTo().parentFrame(); |
| Good to know | manage().window().minimize() / fullscreen() / setSize() | Other window controls. | driver.manage().window().setSize(new Dimension(1280, 800)); |
| Good to know | manage().timeouts().pageLoadTimeout(Duration) | How long to wait for a page to load before failing. | |
| Good to know | manage().getCookies() / addCookie() / deleteAllCookies() | Read, add and clear cookies. deleteAllCookies() gives a fresh start. | driver.manage().deleteAllCookies(); |
Importantclose() vs quit()
close() closes one window. quit() closes everything. Interviewers ask this difference very often.
ImportantfindElement vs findElements
findElement gives one element and fails if none is found. findElements gives a list and never fails. Use findElements(...).size() == 0 to check that something is NOT on the page.
5. Locators: how to find an element
A web page is HTML. Each element has attributes like id, name, class. To click a button, you first find it with a locator from the By class.
<input id="email" name="email" type="email" class="form-control" placeholder="you@example.com">
<button type="submit" class="btn">Login</button>What it does: Example HTML we will find elements in.
| Method | What it does | Example | |
|---|---|---|---|
| Important | By.id("email") | Finds by the id attribute. Fastest and best when the id is unique and stable. | driver.findElement(By.id("email")) |
| Important | By.cssSelector("input#email") | Finds with a CSS selector. Fast and flexible. | By.cssSelector("button[type='submit']") |
| Important | By.xpath("//button[text()='Login']") | Finds with XPath. Most powerful: can use text and parent / child. | By.xpath("//input[@id='email']") |
| Important | By.name("email") | Finds by the name attribute. | By.name("email") |
| Important | By.linkText("Forgot password?") | Finds a link by its full visible text. | By.linkText("Sign up") |
| Good to know | By.partialLinkText("Forgot") | Link text that contains the given part. | |
| Good to know | By.className("btn") | Finds by one class name. Many elements often share a class. | By.className("btn") |
| Good to know | By.tagName("a") | Finds by tag. Used to get lists like all links or all rows. | driver.findElements(By.tagName("a")) |
| Good to know | RelativeLocator.with(By.tagName("input")).below(By.id("email")) | Selenium 4: find by position (above, below, toLeftOf, toRightOf, near). |
CSS and XPath cheat sheet
| Want to find | CSS selector | XPath |
|---|---|---|
| By id | #email | //*[@id='email'] |
| By class | .btn | //*[@class='btn'] |
| Tag with attribute | input[type='email'] | //input[@type='email'] |
| By exact text | not possible | //button[text()='Login'] |
| By part of text | not possible | //button[contains(text(),'Log')] |
| Part of an attribute | div[class*='err'] | //div[contains(@class,'err')] |
| Starts with | input[id^='user'] | //input[starts-with(@id,'user')] |
| Child inside parent | #product-3 button | //div[@id='product-3']//button |
| Second item of many | li:nth-child(2) | (//li)[2] |
| Next to a label | not possible | //label[text()='Email']/following::input[1] |
| Two conditions | input[type='radio'][value='UPI'] | //input[@type='radio' and @value='UPI'] |
ImportantLocator rules
- Order of choice: id first, then CSS, then XPath.
- XPath that starts with
//is relative and safe. XPath like/html/body/div[2]/div[1]/buttonis absolute and breaks when the page changes a little. - Get a locator: right click the element in Chrome, click Inspect, read its attributes. Test your locator in the Console:
$x("//button[text()='Login']")for XPath and$$("#email")for CSS. - If an id changes on every load (like
btn_8374), usestarts-with()orcontains().
6. WebElement methods (the things on the page)
| Method | What it does | Example | |
|---|---|---|---|
| Important | click() | Clicks the element. | driver.findElement(By.id("loginBtn")).click(); |
| Important | sendKeys(CharSequence...) | Types text. Also sends special keys. On a file input, sends the file path. | box.sendKeys("hello"); |
| Important | clear() | Removes the text of an input. | box.clear(); |
| Important | getText() | Returns the visible text of the element. | String msg = el.getText(); |
| Important | getAttribute("name") | Returns an attribute or property such as value, href, class, type. To read what a user typed, use getAttribute("value"). | box.getAttribute("value"); |
| Important | isDisplayed() | True if the element is visible on the page. | el.isDisplayed(); |
| Important | isEnabled() | True if the element is not disabled. Use for buttons. | btn.isEnabled(); |
| Important | isSelected() | True if a checkbox, radio button or option is selected. | box.isSelected(); |
| Good to know | getDomAttribute("name") | Selenium 4: the attribute exactly as written in the HTML. | el.getDomAttribute("data-state"); |
| Good to know | getDomProperty("name") | Selenium 4: the live property value of the element. | el.getDomProperty("value"); |
| Good to know | getTagName() | The tag, such as input. | |
| Good to know | getCssValue("color") | A style value. Used for colour or font checks. | el.getCssValue("color"); |
| Good to know | getSize() / getLocation() / getRect() | Width, height and position on the page. | |
| Good to know | submit() | Submits the form that contains the element. | |
| Good to know | findElement(By) / findElements(By) | Search only inside this element (a child search). | card.findElement(By.tagName("button")); |
| Good to know | getScreenshotAs(OutputType.FILE) | Screenshot of just this element. |
ImportantCommon mix-ups
getText()gives text you can SEE. A hidden element returns an empty string.getAttribute("value")gives what is INSIDE a text box.getText()on a text box returns empty. This confuses everyone once.isDisplayed()throwsNoSuchElementExceptionif the element is not in the HTML at all. UsefindElements(...).size()for that case.
7. Special elements
Dropdown: the Select class
Works only for the HTML <select> tag. Wrap the element in Select.
Select country = new Select(driver.findElement(By.id("country")));
country.selectByVisibleText("India"); // choose by the text the user sees
country.selectByValue("USA"); // choose by the value attribute
country.selectByIndex(0); // choose by position, starts at 0
String chosen = country.getFirstSelectedOption().getText();What it does: Selects an option in three ways and reads the chosen one.
| Method | What it does | Example | |
|---|---|---|---|
| Important | selectByVisibleText(String) | Choose by the visible text. | |
| Important | selectByValue(String) | Choose by the value attribute. | |
| Important | getOptions() | List of all options as List<WebElement>. | select.getOptions().size(); |
| Important | getFirstSelectedOption() | The selected option (single dropdown). | |
| Good to know | selectByIndex(int) | Choose by position. | |
| Good to know | isMultiple() | True for a multi-select list. | |
| Good to know | getAllSelectedOptions() | All selected options of a multi-select. | |
| Good to know | deselectAll() / deselectByValue() / deselectByVisibleText() | Only for multi-select lists. |
Popups: the Alert interface
Alert alert = wait.until(ExpectedConditions.alertIsPresent()); // wait for the popup
String text = alert.getText(); // read the message
alert.accept(); // press OK
// alert.dismiss(); // press Cancel
// alert.sendKeys("abc"); // type into a prompt boxWhat it does: Waits for a popup, reads it, and presses OK.
ImportantAlert rule
While a popup is open, Selenium cannot do anything else on the page. You will get UnhandledAlertException. Always handle the popup first.
Mouse and keyboard: the Actions class
Actions actions = new Actions(driver);
actions.moveToElement(menu).perform(); // hover
actions.doubleClick(button).perform(); // double click
actions.contextClick(item).perform(); // right click
actions.dragAndDrop(source, target).perform(); // drag and drop
actions.sendKeys(Keys.ESCAPE).perform(); // press a keyWhat it does: Every Actions chain must end with perform(), or nothing happens.
Special keys
box.sendKeys("selenium", Keys.ENTER); // type and press Enter
box.sendKeys(Keys.BACK_SPACE); // delete one character
box.sendKeys(Keys.chord(Keys.CONTROL, "a")); // Ctrl + A (use Keys.COMMAND on Mac)What it does: Keys has ENTER, TAB, ESCAPE, BACK_SPACE, ARROW_UP, ARROW_DOWN and more.
JavascriptExecutor
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("arguments[0].scrollIntoView(true);", element); // scroll to element
js.executeScript("arguments[0].click();", element); // click with JavaScript
js.executeScript("arguments[0].value = arguments[1];", dateBox, "2000-05-20"); // set a value
Object title = js.executeScript("return document.title;");What it does: Runs JavaScript inside the page. arguments[0] is the first value you pass after the script text.
Use it when normal Selenium cannot do the job: date pickers, hidden elements, scrolling, or a click that is blocked by another element. Try normal Selenium first.
Frames, windows and file upload
driver.switchTo().frame("payFrame"); // go inside an iframe
driver.switchTo().defaultContent(); // come back to the main page
String main = driver.getWindowHandle(); // remember the main window id
for (String h : driver.getWindowHandles()) {
if (!h.equals(main)) driver.switchTo().window(h); // move to the new tab
}
driver.findElement(By.id("resume")).sendKeys("/Users/me/resume.pdf"); // file uploadWhat it does: For an <input type="file"> you do NOT click. You send the full path of the file as text.
Screenshot
File shot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
Files.copy(shot.toPath(), Path.of("failure.png"));What it does: Saves a picture of the page. Normally done when a test fails.
8. Waits: the most important topic
ImportantWhy we need waits
A page needs time to load. If your code looks for a button before it appears, you get NoSuchElementException. A wait tells Selenium to give the page a little time. Without waits, tests fail randomly.
| Wait | How it works | Advice |
|---|---|---|
| Implicit | One global rule: for every findElement, keep trying up to N seconds. | Simple, but do not mix with explicit waits. |
Explicit (WebDriverWait) | Wait for ONE condition on ONE element (visible, clickable...). | Best choice. Use this. |
| Fluent | Explicit wait where you also set how often to check and which errors to ignore. | For special cases. |
| Thread.sleep | Stops for a fixed time. | Avoid. It always waits the full time. |
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(5));What it does: Implicit wait: for the whole test, find calls retry for up to 5 seconds.
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement msg = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("msg")));
wait.until(ExpectedConditions.elementToBeClickable(By.id("payBtn"))).click();What it does: Waits up to 10 seconds, but continues the moment the condition is true. If it never becomes true, a TimeoutException is thrown.
| Method | What it does | Example | |
|---|---|---|---|
| Important | visibilityOfElementLocated(By) | Element is in the page and visible. Returns the element. | |
| Important | elementToBeClickable(By) | Element is visible and enabled, ready to click. | |
| Important | presenceOfElementLocated(By) | Element exists in the HTML (it may still be hidden). | |
| Important | alertIsPresent() | A popup is open. | |
| Good to know | invisibilityOfElementLocated(By) | Element has gone (for example a loading spinner). | |
| Good to know | textToBePresentInElementLocated(By, text) | Element contains the given text. | |
| Good to know | titleContains(text) / urlContains(text) | Page title or URL has the text. | |
| Good to know | frameToBeAvailableAndSwitchToIt(...) | Frame is ready and Selenium switches into it. | |
| Good to know | numberOfElementsToBe(By, n) | Exactly n elements found. | |
| Good to know | stalenessOf(element) | The old element is gone from the page. |
Wait<WebDriver> fluent = new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(20))
.pollingEvery(Duration.ofMillis(500))
.ignoring(NoSuchElementException.class);
WebElement el = fluent.until(d -> d.findElement(By.id("late")));What it does: Fluent wait: check every 500 ms for up to 20 seconds, and ignore "not found" errors while waiting.
9. Common exceptions
| Exception | Why it happens | Fix | |
|---|---|---|---|
| Important | NoSuchElementException | Element not found. Wrong locator, page not loaded, or inside a frame. | Fix the locator, add a wait, switch to the frame. |
| Important | TimeoutException | An explicit wait never became true. | Check condition and locator. |
| Important | StaleElementReferenceException | The page refreshed and your saved element is old. | Find the element again. |
| Important | ElementClickInterceptedException | Another element (banner, popup) covers the button. | Close the cover, scroll, or wait. |
| Good to know | ElementNotInteractableException | Element exists but is hidden or disabled. | Wait until visible or enabled. |
| Good to know | NoAlertPresentException | You switched to an alert but none is open. | Wait for alertIsPresent(). |
| Good to know | UnhandledAlertException | A popup is open and blocks the next action. | Accept or dismiss it first. |
| Good to know | SessionNotCreatedException | Browser and driver versions do not match. | Update Selenium or Chrome. |
10. TestNG: running and checking tests
Selenium only controls the browser. TestNG runs your methods as tests and tells you pass or fail. It works like JUnit with extra features.
| Method | What it does | Example | |
|---|---|---|---|
| Important | @Test | Marks a method as a test. | |
| Important | @BeforeMethod / @AfterMethod | Run before and after EACH test. Use for opening and closing the browser. | |
| Important | @DataProvider | Gives many rows of data to one test method (data-driven testing). | @Test(dataProvider = "logins") |
| Good to know | @BeforeClass / @AfterClass | Run once for the whole class. | |
| Good to know | @BeforeSuite / @AfterSuite | Run once for the whole run. | |
| Good to know | @Test(priority = 1, enabled = false, groups = "smoke") | Order, skipping and grouping of tests. | |
| Good to know | testng.xml | A file to choose which tests to run, run in parallel, or pass parameters. |
| Method | What it does | Example | |
|---|---|---|---|
| Important | Assert.assertEquals(actual, expected) | Fails if the two values differ. Note the order: actual first, expected second. | Assert.assertEquals(driver.getTitle(), "Home"); |
| Important | Assert.assertTrue(condition) / assertFalse | Fails if the condition is not true / not false. | Assert.assertTrue(btn.isEnabled()); |
| Important | Assert.assertEquals(actual, expected, "message") | The message shows when the test fails. Always add one for clear reports. | |
| Good to know | Assert.assertNotNull / assertNull / fail() | Other checks. | |
| Good to know | SoftAssert | Collects ALL failures and reports them together when you call assertAll(). A normal Assert stops at the first failure. | soft.assertAll(); |
The base class used in the three cases below
package selenium;import org.openqa.selenium.WebDriver;import org.openqa.selenium.chrome.ChromeDriver;import org.openqa.selenium.chrome.ChromeOptions;import org.openqa.selenium.support.ui.WebDriverWait;import org.testng.annotations.AfterMethod;import org.testng.annotations.BeforeMethod;import java.time.Duration;public class BaseTest { protected WebDriver driver; protected WebDriverWait wait; @BeforeMethod public void setUp() { ChromeOptions options = new ChromeOptions(); if (Boolean.getBoolean("headless")) { options.addArguments("--headless=new"); // run without showing a window } options.addArguments("--window-size=1280,900"); driver = new ChromeDriver(options); // opens Chrome wait = new WebDriverWait(driver, Duration.ofSeconds(5)); } @AfterMethod(alwaysRun = true) public void tearDown() { if (driver != null) { driver.quit(); // closes the browser even if the test failed } }}
| Lines | What this part does |
|---|---|
| 1-11 | Package and imports: classes borrowed from Selenium, TestNG and Java. |
| 12-13 | The parent class of all test classes. Other classes say extends BaseTest and get the browser setup for free (inheritance). |
| 14-16 | driver controls the browser. wait is a ready explicit wait of 5 seconds. They are protected so child classes can use them. |
| 17-18 | TestNG runs this before every test method. |
| 19-23 | Settings for Chrome. With -Dheadless=true Chrome runs without a window. The window size is fixed so the layout is the same on every machine. |
| 24 | Starts Chrome. Selenium Manager downloads the right chromedriver if needed. |
| 25-27 | Creates the wait: up to 5 seconds for any condition. |
| 28-34 | Runs after every test, even if the test failed (alwaysRun = true). quit() closes the browser and ends the session. |
11. Page Object Model (design pattern)
Good to knowPOM
Without POM, every test has locators inside it. If a locator changes, you edit many tests. In POM each page gets one class holding its locators and actions. Tests only call these actions. The three cases below keep things simple, so the locators and helper methods sit inside each test class. A real project would move them into page classes like this:
public class LoginPage {
private final WebDriver driver;
private final By email = By.id("email");
private final By password = By.id("pass");
private final By loginBtn = By.id("loginBtn");
public LoginPage(WebDriver driver) { this.driver = driver; }
public void loginAs(String user, String pass) {
driver.findElement(email).sendKeys(user);
driver.findElement(password).sendKeys(pass);
driver.findElement(loginBtn).click();
}
}
// in a test: new LoginPage(driver).loginAs("qa@test.com", "Test@1234");What it does: Locators are private and hidden (encapsulation). If the id changes, you fix one line.
12. Three real automations
Click a case below to open it. In each case you will see: the practice page, where every element is, the manual test cases, the full Java code with line numbers, and the code explained line by line. Read the manual test cases first, then the code. That is how real automation is done: manual thinking first, code second.
ImportantBefore you start
- Open each practice page in Chrome and test it by hand first. The page links are inside each case below.
PAGE_URLin each test already points to the live practice page on GitHub Pages, so you only need internet. To use your own copy, open the file fromdocs/demo-pages/and put itsfile:///path inPAGE_URL.- Run with Maven:
mvn test -Dtest=PasswordTest. Remove-Dheadless=trueto watch the browser work. - All three cases were run and passed (66 checks) on the pages as given.
Case 1: Password validation (a full validation spec) Boundary values, equivalence partitions, negative and state testing on one password form.
Open the page, click around like a manual tester first, then look at the locators below.
Open demo-password.htmlThe test already points to this live page (PAGE_URL), so it runs from any computer with internet. The same file is in the repository at docs/demo-pages/demo-password.html.
ImportantWhat you will learn from this case
- Data-driven tests (one method runs many times with different data)
- Boundary value analysis and equivalence partitioning in real code
- Checking one rule at a time
- Reading attributes and the enabled / disabled state of a button
| Element | HTML on the page | By.id | CSS selector | XPath |
|---|---|---|---|---|
| Username box | <input id="username" type="text"> | username | #username | //input[@id='username'] |
| Password box | <input id="password" type="password"> | password | input[type='password'] | //input[@id='password'] |
| Confirm password box | <input id="confirmPassword" type="password"> | confirmPassword | #confirmPassword | //label[text()='Confirm password']/following::input[1] |
| Show / Hide button | <button id="togglePwd">Show</button> | togglePwd | #togglePwd | //button[text()='Show'] |
| One rule line | <li id="rule-length" data-state="pending"> | rule-length | li[data-state='pass'] | //li[@id='rule-length'] |
| Strength text | <span id="strength">Not entered</span> | strength | #strength | //span[@id='strength'] |
| Error message | <div id="pwdError" class="error"> | pwdError | #pwdError | //div[@id='pwdError'] |
| Create password button | <button type="submit" id="submitBtn" disabled> | submitBtn | button[type='submit'] | //button[@type='submit'] |
| Success message | <div id="successMsg" hidden> | successMsg | #successMsg | //div[contains(text(),'created successfully')] |
Automation follows manual testing. We first decide what to test, and only then write code.
| ID | Technique used | Input | Expected result |
|---|---|---|---|
| TC01 | State check | Nothing typed | Button disabled, strength "Not entered", rules pending |
| TC02-05 | Positive + boundary | Abcd@1234, Abc@1234 (8 chars), Abcdefgh@1234567 (16 chars), Str0ng@Passw0rd | All rules pass, Medium or Strong, button enabled, success message |
| TC06-07 | Boundary value | 7 characters, 17 characters | Only the length rule fails |
| TC08-11 | Equivalence partition | No uppercase, no lowercase, no digit, no special character | Only that one rule fails |
| TC12 | Error guessing | A space inside the password | No-space rule fails |
| TC13 | Business rule | Username ubed, password MyUBED@123 | No-username rule fails |
| TC14-16 | Error guessing (hostile input) | SQL text, script text, only spaces | Rejected, button disabled, no popup |
| TC17-19 | Negative: confirm password | Different, different only in case, empty | Error message, button disabled |
| TC20-21 | State transition | Valid then invalid then valid; username changed later | Button enabled or disabled to match the data |
| TC22 | UI behaviour | Click Show / Hide | Field type switches between password and text |
File: selenium/PasswordTest.java
package selenium;import org.openqa.selenium.By;import org.openqa.selenium.WebElement;import org.testng.Assert;import org.testng.annotations.DataProvider;import org.testng.annotations.Test;import org.testng.asserts.SoftAssert;public class PasswordTest extends BaseTest { // Live practice page hosted on GitHub Pages (works from any computer with internet) private static final String PAGE_URL = "https://ubedzz49.github.io/qa-interview-prep/demo-pages/demo-password.html"; private static final String[] ALL_RULES = {"length", "upper", "lower", "number", "special", "nospace", "nouser"}; // ---------- Locators: all in one place ---------- private final By username = By.id("username"); private final By password = By.id("password"); private final By confirm = By.id("confirmPassword"); private final By submitBtn = By.id("submitBtn"); private final By toggleBtn = By.id("togglePwd"); private final By strength = By.id("strength"); private final By pwdError = By.id("pwdError"); private final By confirmError = By.id("confirmError"); private final By successMsg = By.id("successMsg"); // ---------- Helper methods ---------- private void open() { driver.get(PAGE_URL); } private void type(By locator, String text) { WebElement field = driver.findElement(locator); field.clear(); if (!text.isEmpty()) { field.sendKeys(text); } } private void enter(String pwd, String confirmPwd, String user) { type(username, user); type(password, pwd); type(confirm, confirmPwd); } private void enter(String pwd) { enter(pwd, pwd, ""); } private String ruleState(String rule) { return driver.findElement(By.id("rule-" + rule)).getDomAttribute("data-state"); } private boolean buttonEnabled() { return driver.findElement(submitBtn).isEnabled(); } // ---------- 1. Initial state ---------- @Test public void tc01_initialState() { open(); Assert.assertFalse(buttonEnabled(), "Button must be disabled at start"); Assert.assertEquals(driver.findElement(strength).getText(), "Not entered"); for (String rule : ALL_RULES) { Assert.assertEquals(ruleState(rule), "pending", "Rule " + rule); } Assert.assertEquals(driver.findElement(pwdError).getText(), ""); Assert.assertFalse(driver.findElement(successMsg).isDisplayed()); } // ---------- 2. Positive cases ---------- @DataProvider(name = "positive") public Object[][] positive() { return new Object[][]{ // id, technique, password, expected strength {"TC02", "Valid typical password", "Abcd@1234", "Medium"}, {"TC03", "Boundary: 8 chars (min)", "Abc@1234", "Medium"}, {"TC04", "Boundary: 16 chars (max)", "Abcdefgh@1234567", "Strong"}, {"TC05", "Strong password (12+ chars)", "Str0ng@Passw0rd", "Strong"}, }; } @Test(dataProvider = "positive") public void positiveCases(String id, String technique, String pwd, String expectedStrength) { open(); enter(pwd); SoftAssert soft = new SoftAssert(); // collect ALL failures, then report for (String rule : ALL_RULES) { soft.assertEquals(ruleState(rule), "pass", id + " rule " + rule); } soft.assertEquals(driver.findElement(strength).getText(), expectedStrength, id + " strength"); soft.assertEquals(driver.findElement(pwdError).getText(), "", id + " error text"); soft.assertTrue(buttonEnabled(), id + " button enabled"); soft.assertAll(); driver.findElement(submitBtn).click(); Assert.assertEquals(driver.findElement(successMsg).getText(), "Password created successfully"); } // ---------- 3. Negative cases: break exactly ONE rule at a time ---------- @DataProvider(name = "negative") public Object[][] negative() { return new Object[][]{ // id, technique, password, username, broken rule {"TC06", "Boundary: 7 chars (min - 1)", "Abc@123", "", "length"}, {"TC07", "Boundary: 17 chars (max + 1)", "Abcdefgh@12345678", "", "length"}, {"TC08", "Partition: no uppercase letter", "abcd@1234", "", "upper"}, {"TC09", "Partition: no lowercase letter", "ABCD@1234", "", "lower"}, {"TC10", "Partition: no number", "Abcd@efgh", "", "number"}, {"TC11", "Partition: no special character", "Abcd12345", "", "special"}, {"TC12", "Error guessing: space inside", "Abcd @1234", "", "nospace"}, {"TC13", "Business rule: contains username", "MyUBED@123", "ubed", "nouser"}, }; } @Test(dataProvider = "negative") public void negativeCases(String id, String technique, String pwd, String user, String broken) { open(); enter(pwd, pwd, user); SoftAssert soft = new SoftAssert(); for (String rule : ALL_RULES) { String expected = rule.equals(broken) ? "fail" : "pass"; soft.assertEquals(ruleState(rule), expected, id + " rule " + rule); } soft.assertEquals(driver.findElement(strength).getText(), "Weak", id + " strength"); soft.assertEquals(driver.findElement(pwdError).getText(), "Password does not meet all the rules"); soft.assertFalse(buttonEnabled(), id + " button must be disabled"); soft.assertAll(); } // ---------- 4. Error guessing: hostile and strange input ---------- @Test public void tc14_sqlInjectionTextIsRejected() { open(); enter("' OR '1'='1"); Assert.assertFalse(buttonEnabled()); Assert.assertFalse(driver.findElement(successMsg).isDisplayed()); } @Test public void tc15_scriptTextShowsNoPopup() { open(); enter("<script>alert(1)</script>"); Assert.assertFalse(buttonEnabled()); // if the text had run as a script, an alert would be open now try { driver.switchTo().alert(); Assert.fail("An unexpected popup appeared"); } catch (org.openqa.selenium.NoAlertPresentException expected) { // good: no popup } } @Test public void tc16_onlySpacesAreRejected() { open(); enter(" "); Assert.assertEquals(ruleState("nospace"), "fail"); Assert.assertFalse(buttonEnabled()); } // ---------- 5. Confirm password ---------- @Test public void tc17_mismatchShowsError() { open(); enter("Abcd@1234", "Abcd@12345", ""); Assert.assertEquals(driver.findElement(confirmError).getText(), "Passwords do not match"); Assert.assertFalse(buttonEnabled()); } @Test public void tc18_caseDifferenceIsMismatch() { open(); enter("Abcd@1234", "abcd@1234", ""); Assert.assertEquals(driver.findElement(confirmError).getText(), "Passwords do not match"); } @Test public void tc19_emptyConfirmKeepsButtonDisabled() { open(); enter("Abcd@1234", "", ""); Assert.assertFalse(buttonEnabled()); } // ---------- 6. State transitions ---------- @Test public void tc20_validInvalidValidChangesButton() { open(); enter("Abcd@1234"); Assert.assertTrue(buttonEnabled()); type(password, "abcd@1234"); // breaks the uppercase rule Assert.assertFalse(buttonEnabled()); type(password, "Abcd@1234"); Assert.assertTrue(buttonEnabled()); } @Test public void tc21_changingUsernameCanBreakValidPassword() { open(); enter("Abcd@1234"); Assert.assertTrue(buttonEnabled()); type(username, "abcd"); Assert.assertEquals(ruleState("nouser"), "fail"); Assert.assertFalse(buttonEnabled()); } // ---------- 7. UI behaviour ---------- @Test public void tc22_showHideTogglesFieldType() { open(); WebElement field = driver.findElement(password); Assert.assertEquals(field.getDomAttribute("type"), "password"); driver.findElement(toggleBtn).click(); Assert.assertEquals(field.getDomAttribute("type"), "text"); Assert.assertEquals(driver.findElement(toggleBtn).getText(), "Hide"); driver.findElement(toggleBtn).click(); Assert.assertEquals(field.getDomAttribute("type"), "password"); }}| Lines | What this part does |
|---|---|
| 1-9 | Package and imports. SoftAssert lets one test report all its failures together. |
| 10-12 | Extends BaseTest, so driver and wait are ready. Every @Test method gets a fresh browser. |
| 13-14 | Address of the practice page. Change PATH/TO to your folder. |
| 15-17 | The seven rule names the page shows. We loop over this array instead of writing seven lines each time. |
| 18-28 | Every element is found by its id and stored once as a By. If an id changes you fix one line. |
| 29-33 | open() loads the page. |
| 34-41 | type() finds the box, clear() removes old text, sendKeys() types the new text. Empty text is skipped. |
| 42-51 | enter() fills username, password and confirm password. The shorter enter(pwd) below it is an overloaded shortcut. |
| 52-55 | Reads the data-state attribute of a rule line. The page sets it to pass, fail or pending. |
| 56-59 | Is the Create password button clickable? Uses isEnabled(). |
| 60-72 | TC01: before typing anything the button is disabled, strength says "Not entered", every rule is pending and there is no error or success text. |
| 73-84 | Data provider with four valid passwords. Two of them are the boundaries 8 and 16 characters. One test method will run four times, once per row. |
| 85-102 | Types the password, then checks all rules pass, the strength text, no error and an enabled button. SoftAssert reports every failed check together. Then it clicks the button and checks the success message. |
| 103-118 | Eight invalid passwords. Each breaks exactly ONE rule: boundary 7 and 17, missing uppercase / lowercase / number / special, a space, and the username rule. |
| 119-134 | For every rule it expects fail only for the broken rule and pass for all others. That proves the page flags the right rule and nothing else. It also checks "Weak", the error text and a disabled button. |
| 135-165 | TC14: SQL text. TC15: script text; if it had run, an alert would be open, so switchTo().alert() would NOT throw NoAlertPresentException. TC16: only spaces. |
| 166-188 | Different confirm password, a difference only in letter case, and an empty confirm password. |
| 189-212 | The button must follow the data: valid, then invalid, then valid again. Changing the username later can also make a valid password invalid. |
| 213-225 | The Show / Hide button changes the field type between password and text, and its own text between Show and Hide. |
mvn test -Dtest=PasswordTest // run this class only
mvn test -Dtest=PasswordTest -Dheadless=true // run without a browser window
mvn test // run every testCase 2: Full page validation (every kind of input) Text boxes, date, radio, checkbox, dropdowns, multi-select, slider, textarea, file upload and popups.
Open the page, click around like a manual tester first, then look at the locators below.
Open demo-form.htmlThe test already points to this live page (PAGE_URL), so it runs from any computer with internet. The same file is in the repository at docs/demo-pages/demo-form.html.
ImportantWhat you will learn from this case
- Every input type and how to handle it
- Dependent dropdowns and multi-select
- Alert and confirm popups
- File upload and boundary testing on file size
| Element | HTML on the page | By.id | CSS selector | XPath |
|---|---|---|---|---|
| Full name box | <input id="fullName" type="text"> | fullName | #fullName | //input[@id='fullName'] |
| Date of birth | <input id="dob" type="date"> | dob | input[type='date'] | //input[@id='dob'] |
| Male radio button | <input type="radio" name="gender" id="genderMale" value="Male"> | genderMale | input[name='gender'][value='Male'] | //input[@name='gender' and @value='Male'] |
| Country dropdown | <select id="country"> | country | select#country | //select[@id='country'] |
| State dropdown (starts disabled) | <select id="state" disabled> | state | select#state | //select[@id='state'] |
| Skills multi-select | <select id="skills" multiple> | skills | select[multiple] | //select[@multiple] |
| English checkbox | <input type="checkbox" name="lang" id="langEnglish" value="English"> | langEnglish | input[name='lang']:checked | //input[@name='lang' and @value='English'] |
| Experience slider | <input type="range" id="experience" min="0" max="20"> | experience | input[type='range'] | //input[@type='range'] |
| Address textarea | <textarea id="address" maxlength="200"> | address | textarea#address | //textarea[@id='address'] |
| Resume file input | <input type="file" id="resume"> | resume | input[type='file'] | //input[@type='file'] |
| Error under a field | <div class="error" id="err-email"> | err-email | #err-email | //div[@id='err-email'] |
| Submit / Reset buttons | <button type="submit" id="submitBtn">Submit</button> | submitBtn / resetBtn | button[type='submit'] | //button[text()='Reset'] |
Automation follows manual testing. We first decide what to test, and only then write code.
| ID | Control | What is tested | Technique |
|---|---|---|---|
| TC01-02 | Whole form | Start state; submit empty form shows 11 error messages | State check, required fields |
| TC03 | Name, email, phone (text) | Length 1, 2, 40, 41; letters only; email formats; phone length and first digit | Boundary value, equivalence partition |
| TC04 | Date of birth (date) | Exactly 18 years, one day short, one day over, future date | Boundary value |
| TC05 | Gender (radio) | Only one can be selected at a time | State check |
| TC06-07 | Country and State (dependent dropdowns) | State list changes with country; disabled again when country is cleared | Dependency check |
| TC08 | Skills (multi-select) | 1 to 3 allowed; 4 and 0 give errors | Boundary value |
| TC09 | Languages (checkboxes) | Tick and untick, count of ticked boxes | State check |
| TC10 | Experience (slider) | Value display at 12 and at maximum 20 | Boundary value |
| TC11-12 | Address (textarea) | Counter, 200 character limit, minimum 10 characters | Boundary value |
| TC13 | Resume (file upload) | PDF, PNG, exactly 2 MB, 2 MB + 1 byte, empty file, cv.pdf.exe | Boundary value, error guessing |
| TC14 | Terms (checkbox) | Must be ticked | Required field |
| TC15 | Submit | Full valid form, alert popup, result text | Positive end-to-end |
| TC16 | Reset | Confirm popup: Cancel keeps data, OK clears it | Popup handling |
File: selenium/FormTest.java
package selenium;import org.openqa.selenium.Alert;import org.openqa.selenium.By;import org.openqa.selenium.JavascriptExecutor;import org.openqa.selenium.WebElement;import org.openqa.selenium.support.ui.ExpectedConditions;import org.openqa.selenium.support.ui.Select;import org.testng.Assert;import org.testng.annotations.DataProvider;import org.testng.annotations.Test;import java.io.IOException;import java.nio.file.Files;import java.nio.file.Path;import java.time.LocalDate;import java.util.Arrays;import java.util.List;import java.util.stream.Collectors;public class FormTest extends BaseTest { // Live practice page hosted on GitHub Pages (works from any computer with internet) private static final String PAGE_URL = "https://ubedzz49.github.io/qa-interview-prep/demo-pages/demo-form.html"; private static final int MB = 1024 * 1024; // ---------- Helper methods ---------- private void open() { driver.get(PAGE_URL); } private void type(String id, String text) { WebElement field = driver.findElement(By.id(id)); field.clear(); field.sendKeys(text); } private void clickSubmit() { driver.findElement(By.id("submitBtn")).click(); } private String error(String field) { return driver.findElement(By.id("err-" + field)).getText(); } // Date and range inputs are awkward to type into, so we set them with JavaScript private void setValueWithJs(String id, String value) { WebElement el = driver.findElement(By.id(id)); ((JavascriptExecutor) driver).executeScript( "arguments[0].value = arguments[1];" + "arguments[0].dispatchEvent(new Event('input', { bubbles: true }));", el, value); } // Create a fake file of a given name and size, return its full path private String makeFile(String name, int sizeInBytes) throws IOException { Path folder = Files.createTempDirectory("upload"); Path file = folder.resolve(name); Files.write(file, new byte[sizeInBytes]); return file.toString(); } // ---------- 1. Initial state ---------- @Test public void tc01_initialState() { open(); Assert.assertFalse(driver.findElement(By.id("state")).isEnabled()); Assert.assertEquals(driver.findElement(By.id("expOut")).getText(), "0"); Assert.assertEquals(driver.findElement(By.id("addrCount")).getText(), "0"); Assert.assertEquals(driver.findElements(By.cssSelector("input[name='gender']:checked")).size(), 0); Assert.assertFalse(driver.findElement(By.id("terms")).isSelected()); Assert.assertFalse(driver.findElement(By.id("result")).isDisplayed()); } // ---------- 2. Empty form: every required field shows an error ---------- @Test public void tc02_emptyFormShowsAllErrors() { open(); clickSubmit(); String[][] expected = { {"fullName", "Full name is required"}, {"email", "Email is required"}, {"phone", "Mobile number is required"}, {"dob", "Date of birth is required"}, {"gender", "Select a gender"}, {"country", "Select a country"}, {"skills", "Select at least one skill"}, {"lang", "Select at least one language"}, {"address", "Address is required"}, {"resume", "Upload your resume"}, {"terms", "You must accept the terms"}, }; for (String[] row : expected) { Assert.assertEquals(error(row[0]), row[1], "Field " + row[0]); } } // ---------- 3. Text inputs: data-driven validation ---------- @DataProvider(name = "textCases") public Object[][] textCases() { String phoneMsg = "Enter a 10 digit number starting with 6, 7, 8 or 9"; String nameLenMsg = "Name must be 2 to 40 characters"; return new Object[][]{ // field, value, expected message, why {"fullName", "A", nameLenMsg, "Boundary: 1 char (min - 1)"}, {"fullName", "Al", "", "Boundary: 2 chars (min)"}, {"fullName", "A".repeat(40), "", "Boundary: 40 chars (max)"}, {"fullName", "A".repeat(41), nameLenMsg, "Boundary: 41 chars (max + 1)"}, {"fullName", "John3", "Name can have only letters, space, dot, hyphen and apostrophe", "Invalid: digit in name"}, {"fullName", "Mary-Ann O'Neil", "", "Valid special characters"}, {"email", "abc", "Enter a valid email address", "Invalid: no @"}, {"email", "abc@mail", "Enter a valid email address", "Invalid: no domain extension"}, {"email", "a b@mail.com", "Enter a valid email address", "Invalid: space"}, {"email", "user@mail.com", "", "Valid email"}, {"phone", "12345", phoneMsg, "Invalid: too short"}, {"phone", "5876543210", phoneMsg, "Invalid: starts with 5"}, {"phone", "98765432101", phoneMsg, "Boundary: 11 digits"}, {"phone", "abcdefghij", phoneMsg, "Invalid: letters"}, {"phone", "9876543210", "", "Valid mobile number"}, }; } @Test(dataProvider = "textCases") public void tc03_textFieldValidation(String field, String value, String message, String why) { open(); type(field, value); clickSubmit(); Assert.assertEquals(error(field), message, why); } // ---------- 4. Date of birth: boundary around 18 years ---------- @DataProvider(name = "dobCases") public Object[][] dobCases() { LocalDate today = LocalDate.now(); return new Object[][]{ {"exactly 18 years today (valid)", today.minusYears(18).toString(), ""}, {"turns 18 tomorrow (invalid)", today.minusYears(18).plusDays(1).toString(), "You must be at least 18 years old"}, {"18 years and 1 day (valid)", today.minusYears(18).minusDays(1).toString(), ""}, {"tomorrow (future date)", today.plusDays(1).toString(), "Date of birth cannot be in the future"}, }; } @Test(dataProvider = "dobCases") public void tc04_dateOfBirth(String why, String date, String message) { open(); setValueWithJs("dob", date); clickSubmit(); Assert.assertEquals(error("dob"), message, why); } // ---------- 5. Radio buttons ---------- @Test public void tc05_onlyOneRadioCanBeSelected() { open(); driver.findElement(By.id("genderFemale")).click(); Assert.assertTrue(driver.findElement(By.id("genderFemale")).isSelected()); driver.findElement(By.id("genderOther")).click(); Assert.assertTrue(driver.findElement(By.id("genderOther")).isSelected()); Assert.assertFalse(driver.findElement(By.id("genderFemale")).isSelected()); Assert.assertEquals(driver.findElements(By.cssSelector("input[name='gender']:checked")).size(), 1); } // ---------- 6. Dropdowns: single and dependent ---------- private List<String> optionTexts(Select select) { return select.getOptions().stream().map(WebElement::getText).collect(Collectors.toList()); } @Test public void tc06_countryChangesStateList() { open(); Select country = new Select(driver.findElement(By.id("country"))); WebElement stateElement = driver.findElement(By.id("state")); country.selectByVisibleText("India"); Assert.assertTrue(stateElement.isEnabled()); Assert.assertEquals(optionTexts(new Select(stateElement)), Arrays.asList("-- Select state --", "Karnataka", "Maharashtra", "Delhi")); country.selectByValue("USA"); Assert.assertEquals(optionTexts(new Select(stateElement)), Arrays.asList("-- Select state --", "California", "Texas")); country.selectByIndex(0); // back to "-- Select country --" Assert.assertFalse(stateElement.isEnabled()); } @Test public void tc07_stateIsRequiredAfterChoosingCountry() { open(); new Select(driver.findElement(By.id("country"))).selectByValue("UK"); clickSubmit(); Assert.assertEquals(error("state"), "Select a state"); } // ---------- 7. Multi-select ---------- @Test public void tc08_multiSelectAllowsOneToThreeSkills() { open(); Select skills = new Select(driver.findElement(By.id("skills"))); Assert.assertTrue(skills.isMultiple()); skills.selectByValue("Selenium"); skills.selectByValue("Git"); clickSubmit(); Assert.assertEquals(error("skills"), ""); skills.selectByValue("Playwright"); skills.selectByValue("SQL"); // now 4 selected = max + 1 clickSubmit(); Assert.assertEquals(error("skills"), "Select at most 3 skills"); skills.deselectAll(); clickSubmit(); Assert.assertEquals(error("skills"), "Select at least one skill"); } // ---------- 8. Checkboxes ---------- @Test public void tc09_languageCheckboxes() { open(); driver.findElement(By.id("langEnglish")).click(); driver.findElement(By.id("langTelugu")).click(); Assert.assertEquals(driver.findElements(By.cssSelector("input[name='lang']:checked")).size(), 2); driver.findElement(By.id("langEnglish")).click(); // click again = untick Assert.assertEquals(driver.findElements(By.cssSelector("input[name='lang']:checked")).size(), 1); } // ---------- 9. Slider ---------- @Test public void tc10_sliderShowsItsValue() { open(); setValueWithJs("experience", "12"); Assert.assertEquals(driver.findElement(By.id("expOut")).getText(), "12"); setValueWithJs("experience", "20"); // max Assert.assertEquals(driver.findElement(By.id("expOut")).getText(), "20"); } // ---------- 10. Textarea ---------- @Test public void tc11_textareaCounterAndLimit() { open(); WebElement box = driver.findElement(By.id("address")); box.sendKeys("Hello"); Assert.assertEquals(driver.findElement(By.id("addrCount")).getText(), "5"); box.clear(); box.sendKeys("x".repeat(205)); // we type 205, the browser stops at 200 Assert.assertEquals(box.getAttribute("value").length(), 200); Assert.assertEquals(driver.findElement(By.id("addrCount")).getText(), "200"); } @Test public void tc12_shortAddressIsRejected() { open(); type("address", "Short"); clickSubmit(); Assert.assertEquals(error("address"), "Address must be at least 10 characters"); } // ---------- 11. File upload ---------- @DataProvider(name = "fileCases") public Object[][] fileCases() { return new Object[][]{ // why, file name, size, expected message {"valid PDF", "resume.pdf", 1000, ""}, {"wrong type (png)", "photo.png", 1000, "Only PDF, DOC or DOCX files are allowed"}, {"exactly 2 MB (max)", "big.pdf", 2 * MB, ""}, {"2 MB + 1 byte (max + 1)", "bigger.pdf", 2 * MB + 1, "File must be 2 MB or smaller"}, {"empty file", "empty.pdf", 0, "File is empty"}, {"double extension", "cv.pdf.exe", 1000, "Only PDF, DOC or DOCX files are allowed"}, }; } @Test(dataProvider = "fileCases") public void tc13_resumeUpload(String why, String name, int size, String message) throws IOException { open(); // For a file input we do NOT click. We send the file path as text. driver.findElement(By.id("resume")).sendKeys(makeFile(name, size)); clickSubmit(); Assert.assertEquals(error("resume"), message, why); } // ---------- 12. Terms checkbox ---------- @Test public void tc14_termsMustBeAccepted() { open(); clickSubmit(); Assert.assertEquals(error("terms"), "You must accept the terms"); driver.findElement(By.id("terms")).click(); clickSubmit(); Assert.assertEquals(error("terms"), ""); } // ---------- 13. Happy path: whole form, plus the alert popup ---------- @Test public void tc15_validFormSubmitsAndShowsResult() throws IOException { open(); type("fullName", "Ravi Kumar"); type("email", "ravi@example.com"); type("phone", "9876543210"); setValueWithJs("dob", "1998-05-20"); driver.findElement(By.id("genderMale")).click(); new Select(driver.findElement(By.id("country"))).selectByVisibleText("India"); new Select(driver.findElement(By.id("state"))).selectByVisibleText("Karnataka"); Select skills = new Select(driver.findElement(By.id("skills"))); skills.selectByValue("Selenium"); skills.selectByValue("Playwright"); driver.findElement(By.id("langEnglish")).click(); driver.findElement(By.id("langHindi")).click(); setValueWithJs("experience", "2"); type("address", "12 MG Road, Bengaluru"); driver.findElement(By.id("resume")).sendKeys(makeFile("ravi.pdf", 500)); driver.findElement(By.id("terms")).click(); clickSubmit(); // The page shows an alert popup. We must handle it before doing anything else. Alert alert = wait.until(ExpectedConditions.alertIsPresent()); Assert.assertEquals(alert.getText(), "Application submitted successfully"); alert.accept(); String json = driver.findElement(By.id("result")).getText(); Assert.assertTrue(json.contains("\"fullName\": \"Ravi Kumar\"")); Assert.assertTrue(json.contains("\"phone\": \"9876543210\"")); Assert.assertTrue(json.contains("\"state\": \"Karnataka\"")); Assert.assertTrue(json.contains("\"experience\": 2")); Assert.assertTrue(json.contains("\"resume\": \"ravi.pdf\"")); } // ---------- 14. Reset button and the confirm popup ---------- @Test public void tc16_resetAsksForConfirmation() { open(); type("fullName", "Ravi Kumar"); new Select(driver.findElement(By.id("country"))).selectByValue("USA"); driver.findElement(By.id("resetBtn")).click(); Alert confirm = wait.until(ExpectedConditions.alertIsPresent()); Assert.assertEquals(confirm.getText(), "Clear the whole form?"); confirm.dismiss(); // press Cancel Assert.assertEquals(driver.findElement(By.id("fullName")).getAttribute("value"), "Ravi Kumar"); driver.findElement(By.id("resetBtn")).click(); wait.until(ExpectedConditions.alertIsPresent()).accept(); // press OK Assert.assertEquals(driver.findElement(By.id("fullName")).getAttribute("value"), ""); Assert.assertFalse(driver.findElement(By.id("state")).isEnabled()); }}| Lines | What this part does |
|---|---|
| 1-20 | Imports. Note Select, Alert, JavascriptExecutor, LocalDate and file classes. Each is used for one type of input. |
| 21-26 | Extends BaseTest. PAGE_URL is the page address. MB is the number of bytes in 1 MB, used for the file size boundaries. |
| 27-46 | Small helpers: open(), type(), clickSubmit() and error(field), which reads the red message under a field (err-<field>). |
| 47-54 | A date box and a slider are hard to type into. This helper sets the value with JavaScript, then fires an input event so the page reacts as if a user did it. |
| 55-61 | Creates a real temporary file with a given name and size. A file input needs a real path on disk. |
| 62-73 | TC01: the State dropdown is disabled, counters show 0, no radio selected, terms not ticked, result hidden. |
| 74-96 | TC02: click Submit on an empty form. A 2D array holds each field and its expected message, and a loop checks all 11. |
| 97-121 | Data provider with 15 rows: boundaries on name length (1, 2, 40, 41), invalid characters, email formats, phone length and first digit. An empty expected message means "no error expected". |
| 122-129 | One method for all 15 rows: type the value into the field whose id is field, click Submit, compare the error text. |
| 130-149 | Dates are calculated from today with LocalDate, so the test never goes out of date. Boundary: exactly 18 years, one day too young, one day older, and a future date. |
| 150-161 | Click a radio, check isSelected(). Picking another one must unselect the first. A CSS selector with :checked counts selected radios. |
| 162-193 | Select wraps a <select>. This is a dependent dropdown: changing the country changes the list of states. getOptions() reads the list; selectByIndex(0) returns to the first option and the State box becomes disabled again. |
| 194-215 | isMultiple() confirms the list allows many choices. Select 2 (valid), then 4 (max + 1, error), then deselectAll() (0, error). |
| 216-226 | Clicking a checkbox toggles it. Count of ticked boxes uses the CSS :checked. |
| 227-236 | The slider value is set with JavaScript and the shown number is checked, including the maximum 20. |
| 237-258 | Types "Hello" and checks the counter. Then types 205 characters: the browser keeps only 200 (maxlength). Another test checks the 10 character minimum. |
| 259-281 | Six cases with name and size. Boundary: exactly 2 MB passes, 2 MB + 1 byte fails. Also a wrong type, an empty file, and a double extension (cv.pdf.exe). |
| 282-292 | Without ticking the terms an error shows. After ticking it, the error clears. |
| 293-331 | Fills every control with valid data. After Submit the page shows an alert; alertIsPresent() waits for it, getText() reads it, accept() presses OK. Then the result text is checked. |
| 332-350 | Reset shows a confirm popup. dismiss() (Cancel) keeps the data. accept() (OK) clears the form and disables the State box. |
mvn test -Dtest=FormTest // run this class only
mvn test -Dtest=FormTest -Dheadless=true // run without a browser window
mvn test // run every testCase 3: Complete flow (login to order confirmation) One user journey: login, search, cart, coupon, checkout, payment and logout.
Open the page, click around like a manual tester first, then look at the locators below.
Open demo-shop.htmlThe test already points to this live page (PAGE_URL), so it runs from any computer with internet. The same file is in the repository at docs/demo-pages/demo-shop.html.
ImportantWhat you will learn from this case
- An end-to-end test with many steps
- Checking calculations (subtotal, discount, total)
- Negative cases inside a flow
- Finding elements inside a card or table row
| Element | HTML on the page | By.id | CSS selector | XPath |
|---|---|---|---|---|
| Login email box | <input id="email" type="email"> | email | #email | //input[@id='email'] |
| Login button | <button type="button" id="loginBtn">Login</button> | loginBtn | #loginBtn | //button[text()='Login'] |
| Search box | <input id="search" placeholder="Search products"> | search | input[placeholder='Search products'] | //input[@placeholder='Search products'] |
| A product card | <div class="product" id="product-2"> | product-2 | .product | //div[@id='product-2'] |
| Add button inside a card | <button id="add-3">Add to cart</button> | add-3 | #product-3 button | //div[@id='product-3']//button |
| Cart link | <button id="cartLink">Cart (<span id="cartCount">0</span>)</button> | cartLink | #cartLink | //button[contains(text(),'Cart')] |
| Quantity of an item | <span id="qty-1">2</span> | qty-1 | #cart-row-1 span | //tr[@id='cart-row-1']//span |
| Remove button | <button id="rm-3">Remove</button> | rm-3 | #rm-3 | //tr[@id='cart-row-3']//button[text()='Remove'] |
| Coupon box | <input id="couponInput" aria-label="Coupon code"> | couponInput | #couponInput | //input[@id='couponInput'] |
| Total amount | <strong id="total"> | total | #total | //strong[@id='total'] |
| UPI radio button | <input type="radio" name="pay" id="payUpi" value="UPI"> | payUpi | input[name='pay'][value='UPI'] | //input[@name='pay' and @value='UPI'] |
| Order id | <strong id="orderId"> | orderId | #orderId | //strong[@id='orderId'] |
Automation follows manual testing. We first decide what to test, and only then write code.
| Step | Action | Expected result | Idea |
|---|---|---|---|
| Login (4 cases) | Empty fields, wrong password, unknown email, password in wrong case | Correct error message, shop page stays hidden | Negative testing |
| 1 | Login with qa@test.com and Test@1234 | Welcome text, 3 products shown | Positive |
| 2 | Search "pen", "zzz"; filter by category | 1 product, "No products found", counts correct | Search and filter |
| 3 | Add Notebook twice and Backpack once | Cart count is 3 | State change |
| 4 | Open the cart | Notebook qty 2, line total 500, subtotal 1700 | Calculation check |
| 5 | Click + then - | Quantity 3 then back to 2 | State change |
| 6 | Coupon ABC, then SAVE10 | Invalid message; then discount 170, total 1530 | Negative then positive |
| 7 | Remove Backpack | Subtotal 500, discount 50, total 450 | Recalculation |
| 8 | Checkout with empty fields, PIN 123, no payment | Three different error messages | Validation |
| 9 | Pay with UPI | Order placed, id like ORD-12345, total 450, cart emptied | End result |
| 10 | Logout | Back to login page | Flow end |
| Extra | Open cart with no items | Checkout button disabled | Rule check |
File: selenium/ShopFlowTest.java
package selenium;import org.openqa.selenium.By;import org.openqa.selenium.Keys;import org.openqa.selenium.WebElement;import org.openqa.selenium.support.ui.ExpectedConditions;import org.openqa.selenium.support.ui.Select;import org.testng.Assert;import org.testng.annotations.DataProvider;import org.testng.annotations.Test;public class ShopFlowTest extends BaseTest { // Live practice page hosted on GitHub Pages (works from any computer with internet) private static final String PAGE_URL = "https://ubedzz49.github.io/qa-interview-prep/demo-pages/demo-shop.html"; private static final String EMAIL = "qa@test.com"; private static final String PASSWORD = "Test@1234"; // ---------- Helper methods ---------- private void type(String id, String text) { WebElement field = driver.findElement(By.id(id)); field.clear(); field.sendKeys(text); } private void click(String id) { driver.findElement(By.id(id)).click(); } private String text(String id) { return driver.findElement(By.id(id)).getText(); } private void login(String email, String password) { type("email", email); type("pass", password); click("loginBtn"); } // "₹1530" -> 1530 private int money(String id) { return Integer.parseInt(text(id).replace("₹", "")); } private int productCount() { return driver.findElements(By.cssSelector(".product")).size(); } // Empty a text box by pressing Backspace (this fires the key events the page listens to) private void emptyBox(String id) { WebElement box = driver.findElement(By.id(id)); int length = box.getAttribute("value").length(); for (int i = 0; i < length; i++) { box.sendKeys(Keys.BACK_SPACE); } } // ---------- Login: negative cases ---------- @DataProvider(name = "badLogins") public Object[][] badLogins() { return new Object[][]{ {"both empty", "", "", "Email and password are required"}, {"wrong password", EMAIL, "wrong", "Invalid email or password"}, {"unknown email", "nobody@test.com", PASSWORD, "Invalid email or password"}, {"password in wrong case", EMAIL, "test@1234", "Invalid email or password"}, }; } @Test(dataProvider = "badLogins") public void loginFails(String why, String email, String password, String message) { driver.get(PAGE_URL); login(email, password); Assert.assertEquals(text("loginError"), message, why); Assert.assertFalse(driver.findElement(By.id("shopView")).isDisplayed()); } // ---------- Complete end-to-end flow ---------- @Test public void endToEndPurchase() { driver.get(PAGE_URL); // 1. Login with valid details login(EMAIL, PASSWORD); wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("shopView"))); Assert.assertEquals(text("welcome"), "Welcome, qa@test.com"); Assert.assertEquals(productCount(), 3); // 2. Search and filter products type("search", "pen"); Assert.assertEquals(productCount(), 1); Assert.assertEquals(driver.findElement(By.cssSelector("#product-2 .name")).getText(), "Pen Pack"); emptyBox("search"); type("search", "zzz"); Assert.assertTrue(driver.findElement(By.id("noResults")).isDisplayed()); emptyBox("search"); Select category = new Select(driver.findElement(By.id("category"))); category.selectByVisibleText("Bags"); Assert.assertEquals(productCount(), 1); category.selectByValue("All"); Assert.assertEquals(productCount(), 3); // 3. Add products to the cart click("add-1"); // Notebook click("add-1"); // Notebook again, so quantity = 2 driver.findElement(By.xpath("//div[@id='product-3']//button")).click(); // Backpack, found with XPath Assert.assertEquals(text("cartCount"), "3"); // 4. Check the cart and the maths click("cartLink"); Assert.assertEquals(text("qty-1"), "2"); Assert.assertEquals(driver.findElement(By.cssSelector("#cart-row-1 .line-total")).getText(), "₹500"); Assert.assertEquals(money("subtotal"), 500 + 1200); Assert.assertEquals(money("discount"), 0); Assert.assertEquals(money("total"), 1700); // 5. Change quantity click("inc-1"); Assert.assertEquals(text("qty-1"), "3"); click("dec-1"); Assert.assertEquals(text("qty-1"), "2"); // 6. Coupon: invalid first, then valid type("couponInput", "ABC"); click("applyCoupon"); Assert.assertEquals(text("couponMsg"), "Invalid coupon code"); Assert.assertEquals(money("discount"), 0); type("couponInput", "SAVE10"); click("applyCoupon"); Assert.assertEquals(text("couponMsg"), "Coupon SAVE10 applied: 10% off"); Assert.assertEquals(money("discount"), 170); Assert.assertEquals(money("total"), 1530); // 7. Remove an item and the total updates click("rm-3"); // remove Backpack Assert.assertEquals(driver.findElements(By.id("cart-row-3")).size(), 0); Assert.assertEquals(money("subtotal"), 500); Assert.assertEquals(money("discount"), 50); Assert.assertEquals(money("total"), 450); // 8. Checkout: validation first click("checkoutBtn"); click("placeOrder"); Assert.assertEquals(text("checkoutError"), "Name and address are required"); type("shipName", "Ravi Kumar"); type("shipAddress", "12 MG Road, Bengaluru"); type("pin", "123"); click("placeOrder"); Assert.assertEquals(text("checkoutError"), "Enter a valid 6 digit PIN code"); type("pin", "560001"); click("placeOrder"); Assert.assertEquals(text("checkoutError"), "Select a payment method"); // 9. Pay and confirm the order click("payUpi"); click("placeOrder"); wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("doneView"))); Assert.assertEquals(text("orderStatus"), "Order placed"); Assert.assertTrue(text("orderId").matches("ORD-\\d{5}"), "Order id format"); Assert.assertEquals(text("orderTotal"), "₹450"); Assert.assertEquals(text("orderPay"), "UPI"); Assert.assertEquals(text("cartCount"), "0"); // cart is emptied // 10. Logout returns to the login page click("logoutBtn"); Assert.assertTrue(driver.findElement(By.id("loginView")).isDisplayed()); Assert.assertFalse(driver.findElement(By.id("topNav")).isDisplayed()); } // ---------- Empty cart rule ---------- @Test public void checkoutIsDisabledForEmptyCart() { driver.get(PAGE_URL); login(EMAIL, PASSWORD); click("cartLink"); Assert.assertTrue(driver.findElement(By.id("emptyCart")).isDisplayed()); Assert.assertFalse(driver.findElement(By.id("checkoutBtn")).isEnabled()); }}| Lines | What this part does |
|---|---|
| 1-11 | Imports. Keys is used to press Backspace. |
| 12-18 | Extends BaseTest. The page address and the valid login are constants. |
| 19-40 | Short helpers so the test reads like a story: type, click, text (visible text by id), and login. |
| 41-49 | money("total") turns the text "₹1530" into the number 1530 so we can compare numbers. |
| 50-57 | Empties a box by pressing Backspace. This fires the keyboard events the search box listens to. |
| 58-68 | Four wrong logins: both empty, wrong password, unknown email, and the password in the wrong letter case. |
| 69-76 | Each row logs in with bad data, then checks the exact error text and that the shop page stays hidden. |
| 77-81 | One long test is one full user journey. The numbered comments are the steps. |
| 82-87 | Log in, wait until the shop page is visible (explicit wait), check the welcome text and 3 products. |
| 88-103 | Search "pen" shows 1 product; a CSS selector with a space (#product-2 .name) finds the name inside the card. "zzz" shows "No products found". The Select class filters by category. |
| 104-109 | Add the Notebook twice. The Backpack button is found with XPath: the button inside the div whose id is product-3. |
| 110-117 | Open the cart and check quantity 2, the line total, and that subtotal 1700 = 500 + 1200. |
| 118-123 | Plus makes quantity 3, minus brings it back to 2. |
| 124-135 | A wrong coupon gives an error and no discount. SAVE10 gives 10% off: discount 170, total 1530. |
| 136-142 | Remove the Backpack. The row must be gone (findElements size 0) and the totals recalculate: 500, 50, 450. |
| 143-157 | Three validation messages in order: empty fields, wrong PIN 123, no payment method. |
| 158-167 | Choose UPI and place the order. Check the status, the order id with a regular expression (ORD- and 5 digits), the amount, and that the cart is empty. |
| 168-173 | Logout returns to the login page and hides the top menu. |
| 174-183 | A separate small test: with an empty cart the Checkout button must be disabled. |
mvn test -Dtest=ShopFlowTest // run this class only
mvn test -Dtest=ShopFlowTest -Dheadless=true // run without a browser window
mvn test // run every test13. Quick interview answers
- Important What is Selenium?
An open-source library to automate web browsers. It supports many languages (Java, Python, C#) and browsers (Chrome, Firefox, Edge, Safari). It does not test desktop or mobile apps (mobile uses Appium). - Important Why
WebDriver driver = new ChromeDriver()and notChromeDriver driver = ...?
WebDriver is an interface. Writing the interface on the left lets you switch to another browser by changing one word on the right. - Important findElement vs findElements?
One element and an exception if none, versus a list that is empty if none. - Important close() vs quit()?
close() closes the current window. quit() closes all windows and ends the session. - Important Implicit vs explicit wait?
Implicit: global, for all elements. Explicit: one condition for one element, withWebDriverWait. Prefer explicit, and never mix the two. - Important Which locator do you prefer?
id first, then CSS, then relative XPath, because they are fast and stable. - Important XPath vs CSS?
XPath can search by text and move to parent elements. CSS is simpler and usually faster. - Important Absolute vs relative XPath?
Absolute starts from the root (/html/...) and breaks easily. Relative starts with//and is safer. - Important How do you handle a dropdown, an alert, a frame, a new window?
Selectclass;switchTo().alert();switchTo().frame();getWindowHandles()+switchTo().window(). - Important How do you handle StaleElementReferenceException?
The page changed and the saved element is old. Find the element again. - Important What is Page Object Model?
A design pattern: one class per page that holds its locators and actions. It reduces repeated code and makes maintenance easy. - Important How does TestNG help?
Runs tests, gives annotations (@Test,@BeforeMethod), assertions, data providers, grouping, parallel runs and reports. - Good to know What is a data provider?
A method that returns rows of data. The test method runs once per row. - Good to know How do you upload a file?
sendKeyswith the file path on the<input type="file">. Do not click it. - Good to know How do you run tests without a visible browser?
Headless mode withChromeOptions.addArguments("--headless=new"). - Good to know What is Selenium Grid?
Runs tests in parallel on many browsers and machines. - Good to know What are Selenium limitations?
Web only, no built-in reports, needs explicit waits, and cannot handle captcha or image checks. - Good to know Selenium vs Playwright?
Playwright has built-in auto-waiting, is often faster, and has built-in tracing. Selenium has the bigger community and more language support. Learn both ideas.