Java · TestNG
E-commerce with Selenium
Login with test users, search/filter/sort products, cart & coupons, a validated checkout, a declined card, order history with cancel and return, a store-manager role and a buggy build.
App under test
🛍️ Nova Shop
- URL
- /shop
- User
- demo_user
- Password
- Demo@123
- Manager
- admin_user
- Coupons
- SAVE10 · FLAT20
- Declined card
- 4000 0000 0000 0002
Scenarios to automate
Work top to bottom — each one adds a new technique.
- 1
Valid & invalid login
Beginnerdemo_user / Demo@123 succeeds; wrong password and empty fields show login-error.
- 2
Locked-out user
Beginnerlocked_user gets “Sorry, this user has been locked out.”
- 3
Slow login
Intermediateslow_user takes ~3s — wait for the URL or product grid, never sleep.
- 4
Route guard
BeginnerVisiting /shop/cart signed out redirects to /shop.
- 5
Search, filter & sort
IntermediateAssert product-count text, no-results state, and that product-price values are sorted.
- 6
Cart badge & quantities
IntermediateAdd/remove from the grid, change qty in the cart, check line-total and totals maths.
- 7
Coupons
IntermediateSAVE10 = 10% off, FLAT20 = $20 off above $100, anything else is rejected.
- 8
Checkout validation
IntermediateSubmit empty to collect error-* messages; fix them one by one.
- 9
Declined card
Advanced4000 0000 0000 0002 shows payment-error; switch to Cash on delivery and succeed.
- 10
Order history
AdvancedOrder id on the success page must appear in orders-table after navigation.
- 11
Cancel, return & buy again
IntermediateCancel a Processing order in cancel-modal; request a return on a Delivered one (a reason is required); reorder-* refills the cart.
- 12
Store manager role
Advancedadmin_user lands on /shop/admin, ships and delivers orders and decides returns; demo_user sees access-denied there.
- 13
Bug hunt
Advancedbuggy_user has 5 seeded defects (sort, search, badge, tax, order total) — write tests that fail for it and pass for demo_user.
- 14
UI vs API
AdvancedPOST /api/shop/quote prices the same cart; assert subtotal, discount, tax and total match the cart page.
Sample Selenium suite
A working starting point that finds elements every way Selenium can — by role, label, text, id, name, CSS, XPath and data-testid — so you practice them all. Pick your language, switch to data-testid only to compare, or try your own in the Locator Playground.
import org.testng.annotations.*;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import java.util.Map;
import org.openqa.selenium.support.ui.*;
import java.time.Duration;
import java.util.List;
import static org.testng.Assert.*;
import static org.openqa.selenium.support.ui.ExpectedConditions.*;
// Each line uses a different By on purpose — the comments name them.
public class NovaShopTest {
static final String BASE = "https://www.practiceqaautomation.com";
WebDriver driver;
WebDriverWait wait;
By tid(String id) { return By.cssSelector("[data-testid='" + id + "']"); }
void type(By by, String text) { wait.until(elementToBeClickable(by)).sendKeys(text); }
void click(By by) { wait.until(elementToBeClickable(by)).click(); }
/** Load a page and wait until the app is interactive (the site sets <html data-app-ready="true">). */
void open(String path) {
driver.get(BASE + path);
wait.until(d -> "true".equals(d.findElement(By.tagName("html")).getDomAttribute("data-app-ready")));
}
@BeforeMethod
public void signIn() {
ChromeOptions options = new ChromeOptions();
// Turn off Chrome's "Save password?" and "password found in a data breach" pop-ups: after a
// login they take the keyboard and mouse away from the page, so later typing and clicks are lost.
options.setExperimentalOption("prefs", Map.of(
"credentials_enable_service", false,
"profile.password_manager_enabled", false,
"profile.password_manager_leak_detection", false));
driver = new ChromeDriver(options);
wait = new WebDriverWait(driver, Duration.ofSeconds(10));
open("/shop");
type(By.id("username"), "demo_user"); // By.id
type(By.name("password"), "Demo@123"); // By.name
click(By.xpath("//button[normalize-space()='Sign in']")); // By.xpath: exact text
wait.until(urlContains("/shop/products"));
}
@AfterMethod(alwaysRun = true)
public void tearDown() { driver.quit(); }
@Test
public void buyHeadphonesWithCoupon() {
type(By.cssSelector("[placeholder='Search products…']"), "headphones"); // By.cssSelector: attribute
wait.until(numberOfElementsToBe(tid("product-card"), 1)); // data-testid
click(By.xpath("//li[@data-testid='product-card'][contains(., 'Aurora Wireless Headphones')]//button")); // scoped XPath
wait.until(textToBe(tid("cart-badge"), "1"));
click(By.cssSelector("a[aria-label^='Cart with']")); // attribute starts-with
type(By.cssSelector("[aria-label='Coupon code']"), "SAVE10");
click(By.xpath("//button[normalize-space()='Apply']"));
wait.until(textToBePresentInElementLocated(tid("discount"), "$13.00"));
click(By.linkText("Checkout")); // By.linkText
type(By.id("first-name"), "Ada");
type(By.name("last-name"), "Lovelace");
type(By.cssSelector("input[type='email']"), "ada@example.com"); // by input type
type(By.id("phone"), "5551234567");
type(By.xpath("//label[normalize-space()='Street address']/following-sibling::input"), "1 Analytical Way"); // via its label
type(By.id("city"), "Austin");
new Select(driver.findElement(By.id("state"))).selectByVisibleText("Texas");
type(By.name("zip"), "73301");
type(By.id("card-name"), "Ada Lovelace");
type(By.cssSelector("[placeholder='4242 4242 4242 4242']"), "4242424242424242");
type(By.cssSelector("[placeholder='MM/YY']"), "1230");
type(By.id("card-cvv"), "123");
click(By.xpath("//label[contains(., 'I agree to the terms')]/input"));
click(By.xpath("//button[starts-with(normalize-space(), 'Place order')]")); // the text ends with the total
// ~2s of "processing" — an explicit wait, not Thread.sleep.
String orderId = wait.until(visibilityOfElementLocated(tid("order-id"))).getText();
assertTrue(orderId.matches("NS-\\d+"), "Unexpected order id: " + orderId);
}
@Test
public void sortsByPriceLowToHigh() {
new Select(wait.until(elementToBeClickable(By.cssSelector("select[aria-label='Sort by']")))).selectByValue("price-asc");
List<Double> prices = driver.findElements(tid("product-price")).stream()
.map(e -> Double.parseDouble(e.getText().replace("$", ""))).toList();
assertEquals(prices, prices.stream().sorted().toList());
}
}