Handling Dropdowns, Checkboxes, and Radio Buttons

8 min read

The basic interactions you've learned — click, sendKeys, clear — handle text inputs and ordinary buttons. The form elements that need specialised treatment are dropdowns, checkboxes, and radio buttons. Each has its own quirks. Native <select> elements get a dedicated Select class with its own API. Custom dropdowns built from divs and lists need plain clicks. Checkboxes need state-aware logic because click() toggles them rather than setting them. This lesson covers all three with runnable Java code.

Native dropdowns — the Select class

The native HTML <select> element is an old part of the platform. Selenium ships a dedicated Select helper that wraps the WebElement and gives you the verbs you'd expect:

import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
 
WebElement countryEl = driver.findElement(By.id("country"));
Select country = new Select(countryEl);
 
// Three ways to pick an option
country.selectByVisibleText("United Kingdom");
country.selectByValue("uk");                 // matches <option value="uk">
country.selectByIndex(2);                    // 0-based; the third <option>
 
// Read the current selection
String selected = country.getFirstSelectedOption().getText();
 
// Read every option
List<WebElement> options = country.getOptions();
for (WebElement option : options) {
    System.out.println(option.getText());
}
 
// Multi-select (only if the underlying <select multiple>)
if (country.isMultiple()) {
    country.selectByVisibleText("Red");
    country.selectByVisibleText("Blue");
    country.deselectByVisibleText("Red");
    country.deselectAll();
}

A few non-obvious rules:

  • The Select class only works on real <select> elements. Wrapping a <div role="listbox"> in Select throws UnexpectedTagNameException. The first thing the constructor does is verify the tag.
  • getFirstSelectedOption() is what you assert on. Calling getText() on the wrapped WebElement returns the entire dropdown's option-list as one big string — not what you want.
  • selectByVisibleText is whitespace-sensitive. "United Kingdom " (trailing space) won't match "United Kingdom". Inspect the option text in DevTools if you're unsure.

Custom dropdowns — just click

Most modern web apps don't use native <select>. They build dropdowns out of <div>, <button>, and <li> so they can style them. Those need plain clicks, not the Select class:

// Open the dropdown
driver.findElement(By.cssSelector("[data-testid='country-dropdown']")).click();
 
// Wait for the option list to render (chapter 3 — for now assume it's instant)
// Click the option
driver.findElement(By.xpath("//li[normalize-space()='United Kingdom']")).click();
 
// Verify by reading the trigger's text
String selected = driver.findElement(By.cssSelector("[data-testid='country-dropdown'] .selected-label"))
                        .getText();

The pattern is always: click to open, click the option, read the trigger to assert. Custom dropdowns often need an explicit wait between opening and clicking (chapter 3) — the option list usually renders asynchronously.

Checkboxes — state-aware clicks

Checkboxes are HTML's most counter-intuitive element to automate. Selenium's click() toggles the checkbox, it doesn't set it. So if you "want it checked" but it's already checked, clicking unchecks it. Always check isSelected() first:

WebElement termsCheckbox = driver.findElement(By.id("terms"));
 
// Make sure it's checked, regardless of starting state
if (!termsCheckbox.isSelected()) {
    termsCheckbox.click();
}
 
// Make sure it's unchecked
if (termsCheckbox.isSelected()) {
    termsCheckbox.click();
}
 
// Verify
Assert.assertTrue(termsCheckbox.isSelected(), "Terms checkbox should be checked");

For Page Object code, factor this into a helper:

public void setCheckbox(WebElement el, boolean checked) {
    if (el.isSelected() != checked) {
        el.click();
    }
}

Cypress and Playwright handle this differently — cy.check() and page.check() are idempotent (checking an already-checked box does nothing). Selenium leaves the responsibility to you.

Radio buttons — click and verify

Radio buttons are easier than checkboxes — clicking a radio is idempotent (clicking the already-selected radio does nothing visible). But within a group, clicking one radio also unchecks any sibling radio in the same group:

WebElement standardShipping = driver.findElement(By.id("shipping-standard"));
WebElement expressShipping = driver.findElement(By.id("shipping-express"));
 
standardShipping.click();
Assert.assertTrue(standardShipping.isSelected());
Assert.assertFalse(expressShipping.isSelected());
 
expressShipping.click();
Assert.assertTrue(expressShipping.isSelected());
Assert.assertFalse(standardShipping.isSelected());  // automatically deselected

Picking a radio from a group dynamically — say, by value attribute:

import java.util.List;
 
List<WebElement> radios = driver.findElements(By.name("shipping-method"));
for (WebElement radio : radios) {
    if ("express".equals(radio.getAttribute("value"))) {
        radio.click();
        break;
    }
}

This pattern shows up whenever the test data drives which radio to pick — chapter 7 (data-driven testing) leans on this heavily.

Quick reference — the form-element matrix

Pin this to your monitor for your first month. The patterns are stable; the muscle memory will follow.

A complete shipping-form test

Putting all four element types into one test against a hypothetical shipping form. We'll use https://practice.expandtesting.com/inputs as a stand-in for "any form with a select"; in your own work you'll target your own app.

package com.mycompany.tests.tests;
 
import io.github.bonigarcia.wdm.WebDriverManager;
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.Select;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
 
import java.util.List;
 
public class FormElementsTest {
 
    WebDriver driver;
 
    @BeforeMethod
    public void setup() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        driver.get("https://practice.expandtesting.com/dropdown");
    }
 
    @Test
    public void shouldSelectFromNativeDropdown() {
        WebElement dropdownEl = driver.findElement(By.id("dropdown"));
        Select dropdown = new Select(dropdownEl);
 
        dropdown.selectByVisibleText("Option 2");
 
        Assert.assertEquals(
            dropdown.getFirstSelectedOption().getText(),
            "Option 2"
        );
    }
 
    @Test
    public void shouldListAllDropdownOptions() {
        Select dropdown = new Select(driver.findElement(By.id("dropdown")));
        List<WebElement> options = dropdown.getOptions();
 
        // First "option" on this site is the placeholder; assert at least 3 real options
        Assert.assertTrue(options.size() >= 3,
            "Should have at least three options including the placeholder");
    }
 
    @Test
    public void shouldToggleCheckboxIdempotently() {
        // Switch to the checkboxes page
        driver.get("https://practice.expandtesting.com/checkboxes");
 
        // First checkbox starts unchecked on this site
        WebElement checkbox = driver.findElement(By.cssSelector("input[type='checkbox']"));
 
        // Make it checked, no matter the starting state
        if (!checkbox.isSelected()) checkbox.click();
        Assert.assertTrue(checkbox.isSelected());
 
        // Make it unchecked
        if (checkbox.isSelected()) checkbox.click();
        Assert.assertFalse(checkbox.isSelected());
    }
 
    @AfterMethod
    public void teardown() {
        if (driver != null) driver.quit();
    }
}

Three tests across two pages. Each one demonstrates the right tool for the right element type.

Comparison with Cypress and Playwright

// Cypress
cy.get("#country").select("United Kingdom");
cy.get("#terms").check();      // idempotent — won't toggle if already checked
cy.get("#shipping-express").check();
 
// Playwright
await page.locator("#country").selectOption("United Kingdom");
await page.locator("#terms").check();   // idempotent
await page.locator("#shipping-express").check();

Cypress's .check() and Playwright's .check() are both idempotent — call them on an already-checked box and nothing happens. Selenium's .click() always toggles. That's why every Selenium codebase has a setCheckbox(el, checked) helper somewhere; the modern frameworks don't need one.

For the canonical reference of every method covered here, see the Selenium tool entry on qa.codes.

⚠️ Common mistakes

  • Wrapping a custom dropdown in Select. new Select(driver.findElement(By.cssSelector("[data-testid='country-dropdown']"))) on a <div>-based dropdown throws UnexpectedTagNameException immediately. The Select class is only for native <select> elements. For custom dropdowns, click the trigger and click the option like any other elements.
  • Calling click() on a checkbox without checking state. A test that starts with checkbox.click() works on first run but breaks on retry — if the previous test left the checkbox checked, this one unchecks it. Always guard: if (!checkbox.isSelected()) checkbox.click();. Or write a setCheckbox(el, true) helper and use it everywhere.
  • Asserting on a radio's getAttribute("checked") instead of isSelected(). The checked attribute reflects only the initial HTML state; it doesn't update when the user clicks. isSelected() reports the current DOM state, which is what you actually care about. Same trap exists for checkboxes.

🎯 Practice task

Drive every form-element type. 30–40 minutes.

  1. Add FormElementsTest from this lesson to your project. Run it. All three tests should pass.
  2. Multi-select practice. Visit https://practice.expandtesting.com/dropdown and find the multi-select element if available, or pick another public site with a <select multiple>. Write a test that selects three options, asserts getAllSelectedOptions().size() == 3, then deselectAll() and asserts the size is back to zero.
  3. Custom dropdown. On a real site you use (Gmail, your own app, any modern e-commerce site), find a styled dropdown that's not a native <select>. Use DevTools to confirm the underlying tag is <div> or <button>, then write a test that clicks-to-open and clicks-to-select. Notice you didn't reach for Select.
  4. Checkbox guard helper. Write a static method Forms.setCheckbox(WebElement el, boolean checked) that uses the if (el.isSelected() != checked) el.click(); pattern. Refactor your shouldToggleCheckboxIdempotently test to call it — Forms.setCheckbox(cb, true), Forms.setCheckbox(cb, false). Run twice; the test should be just as green on the second run as the first.
  5. Radio group iteration. Find any radio group on a public page (https://practice.expandtesting.com/radio-buttons or your own app). Write a parameterised test (you can use a simple loop for now, @DataProvider is in chapter 5) that:
    • Iterates through every radio
    • Clicks it
    • Asserts only that radio is selected and all others are not
  6. Stretch — auto-detect. Write a helper selectFromAnyDropdown(WebDriver driver, By trigger, String optionText) that detects whether the underlying element is a <select> (use Select) or a custom dropdown (click-then-click). Test it on both a native and a custom dropdown. This is the kind of utility that earns its keep across a 200-page-object framework.

Chapter 2 is done. You've now got the full locator toolkit and the interaction methods to drive any form on any page. Chapter 3 is where Selenium's biggest tool lives: synchronisation. Every test you write from here on will rely on WebDriverWait to handle the asynchronous nature of modern web apps — the difference between the suite that's flaky once a week and the suite you can trust.

// tip to track lessons you complete and pick up where you left off across devices.