Selenium Java Tutorial: Automate Login Testing
Build reliable Selenium login tests in Java with JUnit, explicit waits, valid and invalid credentials, and practical troubleshooting.
To automate login testing with Selenium and Java, use WebDriver to open the login form, enter test-only credentials, submit it, wait for a visible application result, and assert that result with a test framework such as JUnit. Use a dedicated test account on a local demo or staging application, keep credentials out of source control, and always close the browser session.
This tutorial covers ordinary HTML form login. HTTP Basic or Digest authentication follows a different flow. WebDriver drives the browser; JUnit supplies test lifecycle and pass/fail assertions. Selenium WebDriver organizing Selenium code
1. Create a Java project and install dependencies
Use Java 11 or later and Maven. Selenium Manager, included with Selenium, can manage a compatible browser driver for common local setups. Install Chrome or another browser supported by your chosen driver.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>login-tests</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>11</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.version>5.11.4</junit.version>
<selenium.version>4.27.0</selenium.version>
</properties>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
; </dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.2</version>
</plugin>
</plugins>
</build>
</project>
Save as pom.xml. Create src/test/java/example/LoginTest.java for the test below. The dependency versions above are an example pinned setup; update them deliberately as part of project maintenance.
2. Configure the test account and target URL
Set the environment variables for the test process. The example assumes a test site with /login, fields with IDs username and password, a submit button, a visible signed-in-indicator on success, and an login-error element on rejection. Replace these placeholders with selectors and states from your app.
export APP_BASE_URL="http://localhost:8080"
export TEST_USERNAME="selenium-test-user"
export TEST_PASSWORD="replace-with-test-only-password"
mvn test
Use a secret store or CI secret variables for shared environments. Do not use a real customer account or production credentials. Avoid printing passwords in logs or assertion messages.
3. Write the successful-login test
package example;
import java.time.Duration;
import java.util.Objects;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import static org.junit.jupiter.api.Assertions.assertTrue;
class LoginTest {
private WebDriver driver;
private String baseUrl;
private String username;
private String password;
@BeforeEach
void setUp() {
baseUrl = requiredEnv("APP_BASE_URL");
username = requiredEnv("TEST_USERNAME");
password = requiredEnv("TEST_PASSWORD");
driver = new ChromeDriver();
}
@AfterEach
void tearDown() {
if (driver != null) {
driver.quit();
}
}
@Test
void validCredentialsShowAuthenticatedState() {
driver.get(baseUrl + "/login");
driver.findElement(By.id("username")).sendKeys(username);
driver.findElement(By.id("password")).sendKeys(password);
driver.findElement(By.cssSelector("button[type='submit']")).click();
WebElement signedIn = new WebDriverWait(driver, Duration.ofSeconds(10))
.until(ExpectedConditions.visibilityOfElementLocated(
By.id("signed-in-indicator")));
assertTrue(signedIn.isDisplayed(), "Expected authenticated-state indicator");
}
private static String requiredEnv(String name) {
String value = System.getenv(name);
if (value == null || value.isBlank()) {
throw new IllegalStateException("Set required environment variable: " + name);
}
return value;
}
}
Run it with mvn test after setting all three variables. The finally-like JUnit teardown runs after the test even when an assertion fails, and quit() closes the whole browser session. The browser and selectors are examples: adapt them to the application under test.
4. Add a rejected-login test
A rejection test proves the application presents the expected failure state for invalid test credentials. Use an account and environment where a failed login is safe and permitted; avoid repeated attempts against systems that lock accounts or trigger real alerts.
@Test
void invalidCredentialsShowLoginError() {
driver.get(baseUrl + "/login");
driver.findElement(By.id("username")).sendKeys("known-test-user");
driver.findElement(By.id("password")).sendKeys("deliberately-invalid-password");
driver.findElement(By.cssSelector("button[type='submit']")).click();
WebElement error = new WebDriverWait(driver, Duration.ofSeconds(10))
.until(ExpectedConditions.visibilityOfElementLocated(By.id("login-error")));
assertTrue(error.getText().contains("Invalid"),
"Expected the application's invalid-credentials message");
}
Place this method in the same class. Make the error assertion match your application’s stable behavior rather than an exact sentence if copy may change. Do not assert only that clicking returned: that says nothing about whether login succeeded or was rejected.
How do I automate login testing with Selenium and Java?
- Start a fresh browser session for each test.
- Open the login route and locate fields using stable IDs or test-specific attributes.
- Enter a dedicated test account’s credentials and submit.
- Wait for a specific visible success or error condition.
- Assert the application state and close the browser with
quit().
Successful and rejected login tests use the same form actions but should wait for different application outcomes:
| Case | Input | Wait for | Assert |
|---|---|---|---|
| Success | Valid test credentials | Authenticated indicator, stable landing state, or app-specific redirect condition | Expected signed-in state is visible |
| Rejection | Invalid test credentials | Visible login error or validation state | Expected rejection is shown and no signed-in state is indicated |
Wait for the application, not just the browser
A navigation can reach its configured document readiness state before client-side code has rendered or updated the login result. Selenium documents that these asynchronous changes can race subsequent commands. Use an explicit wait that polls for the condition your test needs. Selenium waiting strategies
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement result = wait.until(
ExpectedConditions.visibilityOfElementLocated(By.id("signed-in-indicator")));
assertTrue(result.isDisplayed());
Choose a condition that proves the result: visibility of the authenticated indicator, presence of a logout control, a known route plus a page marker, or visibility of the rejection message. A URL change by itself may not prove that the application finished rendering.
A fixed sleep pauses for the same duration whether the page responds immediately or slowly, making tests slower and still vulnerable to timing variation. A condition wait continues as soon as the condition is met and times out with a clear failure if it never appears.
Selenium warns against mixing implicit and explicit waits because combined timeout behavior can be unpredictable. This example leaves the implicit wait at its default and uses explicit waits for result conditions. If your project chooses implicit waits instead, avoid mixing strategies and account for their global effect on element lookup.
Make selectors stable and failures diagnosable
- Ask the application team for stable IDs or dedicated attributes such as
data-testid. CSS classes used only for styling and positional selectors tend to change more often. - Scope selectors to the login form if the page has multiple matching controls.
- For dynamic forms, wait for the submit button to be clickable or for an overlay to disappear before clicking.
- On timeout, capture the current URL and a sanitized screenshot or page source in CI artifacts. Ensure artifacts do not expose entered credentials, personal data, or session tokens.
- Keep each test independent. Start with a clean session and do not rely on another test having logged in first.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
NoSuchElementException for a field |
Wrong selector, wrong route, or the field has not rendered yet | Verify the page URL and DOM; use an app-owned stable selector and wait for the field to be visible. |
TimeoutException waiting for success |
Login was rejected, the post-login selector differs, an overlay blocked submission, or the app did not finish rendering | Inspect sanitized page state and logs; confirm test credentials, selector, and expected condition before changing the timeout. |
| Test passes after click without proving login | Only the click command was checked | Wait for and assert a stable authenticated indicator or explicit error state. |
ElementClickInterceptedException |
A banner, modal, or overlay covers the button | Handle the app’s consent/modal state explicitly, wait for the overlay to close, then click. |
| Driver or browser startup error | Browser missing, incompatible, or driver management cannot resolve it in the environment | Install a supported browser, check network/proxy restrictions, and configure a matching driver or Selenium Manager environment. |
| Flaky result timing | Fixed sleep, overly broad wait, or mixed wait strategies | Wait for the exact outcome condition and do not mix implicit and explicit waits. |
| Second test unexpectedly remains logged in | Shared browser session or persistent profile/cookies | Create a fresh WebDriver per test and avoid reusing a profile unless persistence itself is under test. |
Form login versus HTTP authentication
This tutorial automates a page with username and password inputs. HTTP Basic and Digest authentication are protocol-level challenges, so they do not necessarily expose an ordinary login form to locate and submit. Selenium maintainer Simon Stewart described form-based authentication as a long-supported browser interaction and Basic or Digest handling as a separate, historically harder case. That 2021 discussion of Selenium 4’s CDP-based credential registration is version- and browser-specific historical context; check current Selenium and browser support before relying on it. Selenium: A Tour of 4, Authentication
Performance, reliability, and running in CI
- Keep the test focused. Login tests should verify authentication outcomes; avoid adding unrelated navigation and setup that makes failures harder to diagnose.
- Use condition waits. They reduce needless delay while preserving a bounded timeout for slow environments.
- Choose timeout values from your app’s expected behavior. A longer timeout can mask a genuine failure and slow the suite; first inspect the page state and test environment.
- Isolate accounts. Parallel tests should not race on a shared account if the application changes account state, rate limits attempts, or invalidates sessions.
- Protect artifacts. Screenshots, browser logs, and DOM dumps can contain sensitive data. Redact or restrict them and avoid capturing password fields after entry.
- Remote execution. Selenium Server or Grid can run browser sessions remotely when your CI needs a separate browser environment; the test logic still needs explicit outcome assertions. Selenium documentation
Browser startup and network latency usually dominate a simple login test. Run only the needed browser matrix for routine checks, and run broader coverage where its maintenance and execution cost is justified. Do not infer a pass from browser readiness alone.
Or skip the browser setup
If your goal is a clean screenshot of a login or post-login page for a bug report, documentation, or visual review, ScreenshotNeo provides a one-call website screenshot API. Selenium remains the right fit when you need to enter credentials and assert application behavior. ScreenshotNeo takes a page capture; do not send account passwords or use it as a substitute for an authentication test.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Replace the target URL with a page you are authorized to capture. See the ScreenshotNeo API documentation for setup and options. ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed; its MCP server lets AI agents take screenshots; and 1,000 screenshots per month are free with no card, with paid plans starting at $5 for 3,000. Only clean shots are billed, and response headers report the page verdict and billing status. Learn more at ScreenshotNeo.
Sign up free for 1,000 screenshots a month, no card required.
FAQ
Does Selenium decide whether the test passes?
No. WebDriver controls the browser. A test framework such as JUnit runs the test and evaluates assertions.
Should I use a real account?
Use a dedicated test account in a test environment. Keep its credentials in environment or CI secret configuration, not source code.
Can I test a login that uses an identity provider?
Only if your test environment supports a safe, deterministic flow. Prefer an application-provided test identity or an approved test integration; do not automate real user accounts or bypass production security controls.
Why not assert only the destination URL?
A URL can change before the application has finished rendering the authenticated state. Pair route checks with a visible, app-specific marker when possible.


