PracticeQAAutomation logoPracticeQAAutomation
Se

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. 1

    Valid & invalid login

    Beginner

    demo_user / Demo@123 succeeds; wrong password and empty fields show login-error.

  2. 2

    Locked-out user

    Beginner

    locked_user gets “Sorry, this user has been locked out.”

  3. 3

    Slow login

    Intermediate

    slow_user takes ~3s — wait for the URL or product grid, never sleep.

  4. 4

    Route guard

    Beginner

    Visiting /shop/cart signed out redirects to /shop.

  5. 5

    Search, filter & sort

    Intermediate

    Assert product-count text, no-results state, and that product-price values are sorted.

  6. 6

    Cart badge & quantities

    Intermediate

    Add/remove from the grid, change qty in the cart, check line-total and totals maths.

  7. 7

    Coupons

    Intermediate

    SAVE10 = 10% off, FLAT20 = $20 off above $100, anything else is rejected.

  8. 8

    Checkout validation

    Intermediate

    Submit empty to collect error-* messages; fix them one by one.

  9. 9

    Declined card

    Advanced

    4000 0000 0000 0002 shows payment-error; switch to Cash on delivery and succeed.

  10. 10

    Order history

    Advanced

    Order id on the success page must appear in orders-table after navigation.

  11. 11

    Cancel, return & buy again

    Intermediate

    Cancel a Processing order in cancel-modal; request a return on a Delivered one (a reason is required); reorder-* refills the cart.

  12. 12

    Store manager role

    Advanced

    admin_user lands on /shop/admin, ships and delivers orders and decides returns; demo_user sees access-denied there.

  13. 13

    Bug hunt

    Advanced

    buggy_user has 5 seeded defects (sort, search, badge, tax, order total) — write tests that fail for it and pass for demo_user.

  14. 14

    UI vs API

    Advanced

    POST /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.

Language
NovaShopTest.java
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());
  }
}

Practicing another tool?