You can find an element. Now do something with it. This lesson covers the methods on WebElement that every Selenium test calls dozens of times: click, sendKeys, clear, getText, getAttribute, isDisplayed, isEnabled, isSelected. Each takes one line. Each has its own gotcha. By the end you'll know how to fill a real-world login form, read state from the page for assertions, and recognise the three exceptions Selenium throws when something goes sideways during interaction.
The interaction methods you'll use every test
When you call driver.findElement(...) you get back a WebElement. That object has a small, focused API:
WebElement emailInput = driver.findElement(By.id("email"));
WebElement passwordInput = driver.findElement(By.id("password"));
WebElement submitButton = driver.findElement(By.cssSelector("[data-testid='submit']"));
// Type text — appends to whatever is already in the field
emailInput.sendKeys("alice@test.com");
// Clear, then type — best practice when the field may have a value already
emailInput.clear();
emailInput.sendKeys("new-email@test.com");
// Click
submitButton.click();
// Submit a form (alternative — finds the surrounding <form> and submits it)
emailInput.submit();A few things worth pinning down on day one:
sendKeysappends. If the input already contains "alice", callingsendKeys("@test.com")produces "alice@test.com". Useclear()first if you want to overwrite.click()waits for theclickevent to fire but doesn't wait for whatever the click triggers. If the click loads a new page, you still need explicit waits before interacting with the next page (chapter 3).submit()walks up to the surrounding<form>and submits it. Useful in a pinch when you don't want to find the submit button explicitly.
Special keys via the Keys enum
sendKeys accepts the Keys enum for non-printing characters:
import org.openqa.selenium.Keys;
emailInput.sendKeys(Keys.CONTROL, "a"); // select all
emailInput.sendKeys(Keys.BACK_SPACE); // delete the selection
emailInput.sendKeys("alice@new.com", Keys.TAB); // type, then tab to next field
emailInput.sendKeys(Keys.ENTER); // submit by pressing Enter
// Chord — Ctrl+A then Delete
emailInput.sendKeys(Keys.chord(Keys.CONTROL, "a"));
emailInput.sendKeys(Keys.DELETE);Keys.chord(...) is how you express "hold these keys together." It's the most common way to do "select all then delete" if the field doesn't respond to a plain clear().
Reading state from the page
Just as critical as typing is reading. Every assertion in your suite ends with one of these:
// Visible text — what the user sees
String text = element.getText();
// Input value — what's currently typed
String value = element.getAttribute("value");
// Other useful attributes
String href = element.getAttribute("href");
String className = element.getAttribute("class");
String dataTestId = element.getAttribute("data-testid");
// Boolean state
boolean visible = element.isDisplayed();
boolean enabled = element.isEnabled();
boolean selected = element.isSelected(); // for checkboxes/radios/options
// Geometry
Dimension size = element.getSize();
Point location = element.getLocation();Three of these warrant special attention:
getText()returns the rendered text — what's actually visible on screen. CSSdisplay: noneelements return an empty string, even if the underlying HTML has text. To read the raw HTML text regardless of CSS, usegetAttribute("textContent").getAttribute("value")is how you read what's currently typed in an input.getText()on an<input>returns an empty string — inputs don't have visible text in the DOM sense.isSelected()only makes sense for checkboxes, radios, and<option>elements. Calling it on a button returnsfalseand tells you nothing useful.
A complete login flow
Putting it together — fill a real form, click submit, read the result:
package com.mycompany.tests.tests;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.By;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class LoginFormTest {
WebDriver driver;
@BeforeMethod
public void setup() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
driver.get("https://www.saucedemo.com");
}
@Test
public void shouldLoginSuccessfully() {
WebElement username = driver.findElement(By.id("user-name"));
WebElement password = driver.findElement(By.id("password"));
WebElement loginButton = driver.findElement(By.id("login-button"));
username.sendKeys("standard_user");
password.sendKeys("secret_sauce");
loginButton.click();
// Read the URL after login to assert success
Assert.assertTrue(
driver.getCurrentUrl().contains("/inventory.html"),
"Should land on the inventory page after login"
);
}
@Test
public void shouldShowErrorOnEmptyCredentials() {
driver.findElement(By.id("login-button")).click();
WebElement error = driver.findElement(By.cssSelector("[data-test='error']"));
Assert.assertTrue(error.isDisplayed());
Assert.assertEquals(
error.getText(),
"Epic sadface: Username is required",
"Sauce Demo's exact error message"
);
}
@Test
public void shouldClearAndTypeAgain() {
WebElement username = driver.findElement(By.id("user-name"));
username.sendKeys("wrong_user");
username.clear(); // wipe what's there
username.sendKeys("standard_user");
Assert.assertEquals(
username.getAttribute("value"),
"standard_user",
"Field should contain the second value, not the first"
);
}
@Test
public void shouldSubmitWithEnterKey() {
driver.findElement(By.id("user-name")).sendKeys("standard_user");
WebElement password = driver.findElement(By.id("password"));
password.sendKeys("secret_sauce");
password.sendKeys(Keys.ENTER); // submit by Enter — no click needed
Assert.assertTrue(driver.getCurrentUrl().contains("/inventory.html"));
}
@AfterMethod
public void teardown() {
if (driver != null) driver.quit();
}
}Four tests. Together they exercise every interaction method we've covered — sendKeys, click, clear, getText, getAttribute("value"), isDisplayed, Keys.ENTER, and the URL read.
The interaction loop, visualised
Step 1 of 5
Find
driver.findElement(By.cssSelector(...)) — locate the element. Throws NoSuchElementException if absent.
This pattern — find, prepare, act, read, assert — is what your tests will look like for the rest of your career. Get fluent at it now.
The three interaction-time exceptions
Three exceptions show up regularly during interaction; each tells you something specific:
NoSuchElementException — the locator didn't match. Either the locator is wrong, or the element hasn't loaded yet.
// Almost always — wait for the element first (chapter 3 covers explicit waits)
driver.findElement(By.id("does-not-exist")); // → NoSuchElementExceptionElementNotInteractableException — the element exists but can't be clicked or typed into. Usually because it's hidden, off-screen, disabled, or covered by another element (a modal, a sticky header, a loading spinner).
WebElement button = driver.findElement(By.id("submit"));
button.click(); // → ElementNotInteractableException if the button is hidden behind a spinnerStaleElementReferenceException — you found the element, but the DOM changed between finding it and using it. Single-page apps re-render constantly; an element that existed 200ms ago may have been replaced by a new (visually identical) element.
WebElement card = driver.findElement(By.cssSelector(".product-card"));
// ... something triggers a re-render ...
card.click(); // → StaleElementReferenceException — the old reference is deadThe fix is almost always to find the element again, immediately before using it:
driver.findElement(By.cssSelector(".product-card")).click(); // single line: find + clickWe'll handle the timing dimension of these — the WebDriverWait machinery — in chapter 3.
Comparison with Cypress and Playwright
// Cypress
cy.get("[data-testid='email']").type("alice@test.com");
cy.get("[data-testid='submit']").click();
// Playwright
await page.getByTestId("email").fill("alice@test.com");
await page.getByTestId("submit").click();Both modern frameworks have auto-waiting baked into every interaction. Selenium does not — findElement and click do not retry. That's why chapter 3 exists. Once you've built the explicit-wait pattern, your code reads only slightly heavier than the JS frameworks.
The Selenium tool entry on qa.codes lists every WebElement method, and the XPath & CSS selectors cheat sheet covers the locator side.
⚠️ Common mistakes
sendKeyswithoutclear()on a pre-filled field. Inputs that the app pre-fills (a remembered email, a default value) get the new text appended, producing strings likealice@test.comnew@test.com. Callclear()first whenever you can't be sure the field is empty. Note:clear()itself can sometimes fail on rich text editors — for those, fall back toKeys.chord(Keys.CONTROL, "a")followed byKeys.DELETE.- Reading input value with
getText().<input>elements have no rendered text content —getText()returns an empty string. UsegetAttribute("value")instead. The number of suites that have at least onegetText()call on an input that should begetAttribute("value")is depressingly high. - Catching
StaleElementReferenceExceptionwith a tight retry loop. If you see staleness, the answer isn'ttry { click } catch { try again }. The answer is "don't hold WebElement references across DOM mutations" — find immediately before use, or use Page Factory's@CacheLookup = false. A retry-on-stale catch hides a design problem and produces flaky tests.
🎯 Practice task
Build a complete form-interaction test on a real site. 30–40 minutes.
- Add
LoginFormTestfrom this lesson to your project. Run all four tests; all should pass. - Add a fifth test: lock-out flow. Use the credentials
locked_out_user/secret_sauce. Sauce Demo refuses login. Assert the error message viaerror.getText(), and assert the URL did not change. - Read every state. On the inventory page (after a successful login), find the cart icon. Use:
isDisplayed()to check it's visiblegetAttribute("class")to dump the classesgetText()to read its visible text (initially empty)getSize()andgetLocation()just to see what they return Print each. Useful exercise — you'll be surprised how often you'll want exactly these reads in real tests.
- Make
StaleElementReferenceExceptionhappen. On the inventory page, find any product card. Click "Add to cart" on a different card. Now try to use the first card's reference. Read the exception. Then refactor the test to find the element fresh each time, and watch it pass. - Use Tab and Enter. Write a test that fills the username and password using only
sendKeysandKeys.TABto move between fields, thenKeys.ENTERto submit. No.click()calls. This is how a keyboard user navigates the page — and great a11y signal too. - Stretch — the
waityou don't yet have. Add a test that immediately tries to find an element on the inventory page right afterloginButton.click(). On a fast machine it works; on a slow CI runner it would intermittently fail. Don't fix it yet — just feel the timing problem. Chapter 3 starts there.
Next lesson: the form elements we haven't covered yet — dropdowns (the <select> element and the dedicated Select class), checkboxes, and radio buttons. Each has its own subtle behaviour that catches beginners.