This recipe is the smallest end-to-end Selenium test that signs a seeded customer into QA Shop in Java.
It walks the public login surface — load the page, fill credentials with the autocomplete-attribute selectors that survive QA Shop's honeypot defense, click submit, and assert the authenticated-only header menu. Run it with JUnit 5 in about eight seconds.
It is the canonical first Java-on-Selenium test to write whenever you need to drive a real session into your suite, and it is the foundation that every authenticated QA Shop recipe (checkout, account settings, order history) builds on top of.
§ 01 · PREREQUISITES
Prerequisites
You need a JDK 17+ project with selenium-java, junit-jupiter, and webdrivermanager on the classpath, plus a Chrome browser available locally and a seeded customer to log in as.
On Maven, add org.seleniumhq.selenium:selenium-java:4.43.0, org.junit.jupiter:junit-jupiter:5.10.2, and io.github.bonigarcia:webdrivermanager:5.9.2. Selenium 4.6+ also ships Selenium Manager natively, which auto-resolves drivers — WebDriverManager is the more idiomatic Java choice and is what this recipe uses.
Set to http://localhost:3002 for a local checkout, or https://automationtestingplatform.com for the hosted demo. The script reads it with a sensible default.
The seeded recipe-verifier@automationtestingplatform.com account ships with the QA Shop database. Locally it is provisioned by supabase/seed.sql; in CI both values live in GitHub repo secrets. The script throws fast if either is missing rather than silently passing.
The recipe assumes the server is already up. The test does not start it for you.
§ 02 · THE SCRIPT
The script
Save this file as src/test/java/LoginTest.java and run it with mvn test -Dtest=LoginTest (or the Gradle equivalent).
// title="LoginTest.java"
import io.github.bonigarcia.wdm.WebDriverManager;
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 java.time.Duration;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class LoginTest {
private WebDriver driver;
private static final String BASE_URL =
System.getenv().getOrDefault("RECIPE_BASE_URL", "http://localhost:3002");
@BeforeEach
void setUp() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
}
@AfterEach
void tearDown() {
if (driver != null) driver.quit();
}
@Test
void seededCustomerCanSignIn() {
String email = System.getenv("RECIPE_AUTH_EMAIL");
String password = System.getenv("RECIPE_AUTH_PASSWORD");
if (email == null || password == null) {
throw new IllegalStateException(
"RECIPE_AUTH_EMAIL and RECIPE_AUTH_PASSWORD must be set."
);
}
driver.get(BASE_URL + "/auth/login");
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30));
WebElement emailInput = wait.until(ExpectedConditions
.visibilityOfElementLocated(By.cssSelector("input[autocomplete='email']")));
emailInput.sendKeys(email);
driver.findElement(By.cssSelector("input[autocomplete='current-password']"))
.sendKeys(password);
// Turnstile is not on /auth/login (scoped to principal register +
// forgot-password only), so the button is enabled on render.
WebElement submitBtn = driver.findElement(
By.xpath("//button[contains(translate(., 'LOGIN', 'login'), 'log in')]")
);
submitBtn.click();
WebElement userMenu = wait.until(ExpectedConditions
.visibilityOfElementLocated(By.cssSelector("[data-testid='header-user-menu']")));
assertTrue(userMenu.isDisplayed());
}
}Three details are worth calling out.
The driver is constructed with WebDriverManager.chromedriver().setup() followed by new ChromeDriver(). WebDriverManager downloads the matching driver binary on first run and caches it — no manual driver management, no version-drift errors. For headless CI you would pass ChromeOptions with --headless=new, but for the smallest possible recipe we stay headed so you can watch the flow on first run.
The credential locators are By.cssSelector("input[autocomplete='email']") and By.cssSelector("input[autocomplete='current-password']"). QA Shop's auth form randomizes the real input name attributes per render as a honeypot defense — name="email" belongs to a hidden decoy input that, if submitted, blocks your IP. The autocomplete attribute is preserved unchanged across renders so password managers and screen readers keep working, and that is the contract we lean on here.
The submit click runs immediately after findElement(...) — no elementToBeClickable wait. QA Shop scopes Cloudflare Turnstile to principal-account register + forgot-password only, so /auth/login renders with an enabled submit button. The XPath //button[contains(translate(., 'LOGIN', 'login'), 'log in')] is the standard XPath 1.0 trick for case-insensitive text match — anchoring on the rendered button label (Log in) is more durable than a brittle CSS class.
§ 03 · FAQ
FAQ
Common questions when running this recipe or adapting it.
Construct ChromeOptions opts = new ChromeOptions(); opts.addArguments("--headless=new", "--no-sandbox", "--disable-dev-shm-usage"); and pass new ChromeDriver(opts). The QA Shop CI harness does the same in TypeScript — see tests/recipes/selenium/setup.ts.
The 30-second WebDriverWait is sized for slow CI runners and the post-login redirect on a cold cache. Drop it to Duration.ofSeconds(10) for a local dev run, or raise it past 30 if you are running against a constrained CI box.
The recipe-verifier@automationtestingplatform.com customer is the canonical fixture for these recipes, but any seeded account with a known password works. Override RECIPE_AUTH_EMAIL and RECIPE_AUTH_PASSWORD at run time and the rest of the script keeps working.
Related
For the same flow in Playwright (TypeScript), see the Playwright login recipe. For the broader Playwright vs Selenium vs Cypress picture, see the QA Shop testing guide.
The Python and TypeScript Selenium login counterparts are coming soon — placeholder slugs are /recipes/selenium/python-login and /recipes/selenium/typescript-login.
Frequently asked questions
Last verified: