Platform

Solutions

Resources

Platform

Solutions

Resources

Platform

Solutions

Resources

Blog

NoSuchElementException in Selenium: Why It Happens + Fix

Rishabh Kumar

Published on

eBook CTA Background
RECOMMENDED RESOURCES

The Ultimate Enterprise Guide To Agentic Testing

AI agents can write, fix and triage your tests at scale. See how to use them in QA while keeping people in control of everything that ships.

Get the eBook →

Enterprise guide to agentic testing
eBook CTA Background
RECOMMENDED RESOURCES

The Ultimate Enterprise Guide To Agentic Testing

AI agents can write, fix and triage your tests at scale. See how to use them in QA while keeping people in control of everything that ships.

Get the eBook →

Enterprise guide to agentic testing
eBook CTA Background
RECOMMENDED RESOURCES
Enterprise guide to agentic testing

The Ultimate Enterprise Guide To Agentic Testing

AI agents can write, fix and triage your tests at scale. See how to use them in QA while keeping people in control of everything that ships.

Get the eBook →

eBook CTA Background
RECOMMENDED RESOURCES

The Ultimate Enterprise Guide To Agentic Testing

AI agents can write, fix and triage your tests at scale. See how to use them in QA while keeping people in control of everything that ships.

Get the eBook →

Enterprise guide to agentic testing

Table of contents

Lorem ipsum

Lorem ipsum

Lorem ipsum

Lorem ipsum

Lorem ipsum

NoSuchElementException is one of the most common exceptions in Selenium WebDriver. It's thrown when your script asks for an element that WebDriver can't find using the locator you gave it.

The usual fixes, like explicit waits, better locators and retries, work up to a point. But they all treat the symptom. The underlying problem is that each test depends on a single locator, and that locator breaks the moment the application changes.

What is NoSuchElementException in Selenium?

NoSuchElementException is thrown by findElement() when WebDriver can't locate an element in the page's DOM (Document Object Model) using the locator you specified. In short, as far as WebDriver can tell, the element isn't there at the moment it looks.

One detail catches people out: findElements(), plural, never throws this exception. If nothing matches, it simply returns an empty list.

Technical Definition

NoSuchElementException is an unchecked exception, which means Java doesn't force you to catch it. It sits within Selenium's exception hierarchy like this:





Typical Error Message:





The message tells you which locator strategy was used (here, a CSS selector) and the exact value that failed (#submit-btn). That's your starting point for debugging.

NoSuchElementException vs StaleElementReferenceException

These two are often confused, but they mean different things:

Exception

What happened

Usually caused by

NoSuchElementException

WebDriver never found the element

A wrong locator, or looking too early

StaleElementReferenceException

WebDriver found the element, but it was later removed or replaced

The page re-rendering after the element was found

Both make tests brittle. NoSuchElementException usually points to a locator or timing problem, while a stale element points to a DOM that changed underneath you.

Why NoSuchElementException Happens: 6 Root Causes

The exception has several causes, and each needs a different fix. Working out which one you're dealing with saves a lot of time compared with trying generic fixes one after another.

  1. Incorrect or Outdated Locators

This is the most common cause. The locator simply doesn't match anything on the page.

Common locator mistakes

// Typo in the ID
driver.findElement(By.id("sumbit-btn"));          // should be "submit-btn"

// Using the wrong strategy
driver.findElement(By.id("btn btn-primary"));     // that's a class list, not an ID

// A class name with spaces
driver.findElement(By.className("btn primary"));  // By.className accepts one class only

// A generated ID that changes every session
driver.findElement(By.id("submit_btn_47392"));    // different next time the page loads

// An absolute XPath that breaks with any layout change
driver.findElement(By.xpath("/html/body/div[2]/div/form/div[3]/button"));
// Typo in the ID
driver.findElement(By.id("sumbit-btn"));          // should be "submit-btn"

// Using the wrong strategy
driver.findElement(By.id("btn btn-primary"));     // that's a class list, not an ID

// A class name with spaces
driver.findElement(By.className("btn primary"));  // By.className accepts one class only

// A generated ID that changes every session
driver.findElement(By.id("submit_btn_47392"));    // different next time the page loads

// An absolute XPath that breaks with any layout change
driver.findElement(By.xpath("/html/body/div[2]/div/form/div[3]/button"));
// Typo in the ID
driver.findElement(By.id("sumbit-btn"));          // should be "submit-btn"

// Using the wrong strategy
driver.findElement(By.id("btn btn-primary"));     // that's a class list, not an ID

// A class name with spaces
driver.findElement(By.className("btn primary"));  // By.className accepts one class only

// A generated ID that changes every session
driver.findElement(By.id("submit_btn_47392"));    // different next time the page loads

// An absolute XPath that breaks with any layout change
driver.findElement(By.xpath("/html/body/div[2]/div/form/div[3]/button"));
// Typo in the ID
driver.findElement(By.id("sumbit-btn"));          // should be "submit-btn"

// Using the wrong strategy
driver.findElement(By.id("btn btn-primary"));     // that's a class list, not an ID

// A class name with spaces
driver.findElement(By.className("btn primary"));  // By.className accepts one class only

// A generated ID that changes every session
driver.findElement(By.id("submit_btn_47392"));    // different next time the page loads

// An absolute XPath that breaks with any layout change
driver.findElement(By.xpath("/html/body/div[2]/div/form/div[3]/button"));

Why this happens

Developers rename IDs, restructure pages or change CSS classes during a sprint, and the test scripts don't get updated. The locator that worked yesterday now points at nothing.

  1. Timing Issues and Lazy Loading

Dynamic content loading:

Modern applications load much of their content after the page first appears, using JavaScript and API calls.

The problem:

Selenium can run faster than the page. If findElement() runs before the element has been added to the DOM, you get NoSuchElementException, even though the element appears a moment later.

driver.get("https://example.com/dashboard");

// The results table loads via an API call after the page opens.
// This line runs before the table exists, so it throws.
WebElement table = driver.findElement(By.id("results-table"));
driver.get("https://example.com/dashboard");

// The results table loads via an API call after the page opens.
// This line runs before the table exists, so it throws.
WebElement table = driver.findElement(By.id("results-table"));
driver.get("https://example.com/dashboard");

// The results table loads via an API call after the page opens.
// This line runs before the table exists, so it throws.
WebElement table = driver.findElement(By.id("results-table"));
driver.get("https://example.com/dashboard");

// The results table loads via an API call after the page opens.
// This line runs before the table exists, so it throws.
WebElement table = driver.findElement(By.id("results-table"));

Single-page applications: React, Vue and Angular apps build the page on the fly. Elements appear in response to clicks, state changes or data arriving, so when an element exists can be hard to predict.

  1. Hidden Elements (A Different Exception)

The element is in the DOM but not visible: CSS rules like display: none, visibility: hidden or opacity: 0 hide elements from users while leaving them in the HTML.

How Selenium behaves: findElement() will still find a hidden element, so you won't get NoSuchElementException. You'll get a different error when you try to use it:

WebElement menu = driver.findElement(By.id("user-menu")); // found, even though hidden
menu.click(); // throws ElementNotInteractableException
WebElement menu = driver.findElement(By.id("user-menu")); // found, even though hidden
menu.click(); // throws ElementNotInteractableException
WebElement menu = driver.findElement(By.id("user-menu")); // found, even though hidden
menu.click(); // throws ElementNotInteractableException
WebElement menu = driver.findElement(By.id("user-menu")); // found, even though hidden
menu.click(); // throws ElementNotInteractableException

Teams often mix these up, especially with elements that only appear after a user action. If the element is found but can't be clicked or typed into, the problem is visibility, not location.

  1. Iframes and Shadow DOM

Iframes: An element inside an iframe lives in a separate document. WebDriver can't see it until you switch into that frame.

// The card number field is inside a payment iframe
driver.findElement(By.name("card-number")); // throws NoSuchElementException
// The card number field is inside a payment iframe
driver.findElement(By.name("card-number")); // throws NoSuchElementException
// The card number field is inside a payment iframe
driver.findElement(By.name("card-number")); // throws NoSuchElementException
// The card number field is inside a payment iframe
driver.findElement(By.name("card-number")); // throws NoSuchElementException

Shadow DOM: Web components can wrap their elements in a shadow root, which hides them from normal locators. Searching for them from the main page fails, even when your selector is correct.

  1. Elements That Only Exist in Certain States

Modern applications change what's on the page depending on what the user does and what data they're looking at. An element that exists in one state may not exist in another.

  • Conditional rendering: Fields that only appear after a checkbox is ticked or an option is chosen

  • Live updates: Dashboards and feeds that keep replacing parts of the page

  • Validation messages: Errors and hints that appear and disappear as users type

  • Pagination and infinite scroll: Items that aren't loaded until the user scrolls or changes page

  • Modals and overlays: Pop-ups that temporarily cover or replace other content

If your test assumes the page is in one state while it's actually in another, the element won't be there.

  1. Changes in the Application Code

Applications change constantly. In a typical sprint, developers might restructure HTML, rename IDs and classes, swap one UI component for another, or even move to a new frontend framework. Any of those can break an existing locator.

Here's a common example. The test was written when the button had id="submit-btn". After a sprint, a developer renames it to id="submit-button". Nothing is wrong with the application, but every test that clicks that button now fails with NoSuchElementException.

On a large suite, this adds up quickly. Teams on traditional frameworks often spend more time repairing locators than writing new tests, and brittle locators are usually the main reason.

How to Fix NoSuchElementException: 7 Proven Methods

Most teams handle NoSuchElementException with a mix of debugging and framework-level fixes. Each method below reduces failures, but each comes with its own trade-offs.

Method 1: Verify and Correct the Locator

A wrong or outdated locator is the most common cause, so always check it first. Open the page in your browser, open DevTools, and test your locator directly in the console:

// In the browser DevTools console

// Test a CSS selector
document.querySelectorAll("#submit-btn");

// Test an XPath
$x("//button[normalize-space()='Submit']");
// In the browser DevTools console

// Test a CSS selector
document.querySelectorAll("#submit-btn");

// Test an XPath
$x("//button[normalize-space()='Submit']");
// In the browser DevTools console

// Test a CSS selector
document.querySelectorAll("#submit-btn");

// Test an XPath
$x("//button[normalize-space()='Submit']");
// In the browser DevTools console

// Test a CSS selector
document.querySelectorAll("#submit-btn");

// Test an XPath
$x("//button[normalize-space()='Submit']");

If the result is empty, the locator is wrong. You can run the same check from your test, which is useful when the page only reaches the right state partway through a script:

int matches = driver.findElements(By.cssSelector("#submit-btn")).size();
System.out.println("Matches found: " + matches); // 0 means the locator is wrong
int matches = driver.findElements(By.cssSelector("#submit-btn")).size();
System.out.println("Matches found: " + matches); // 0 means the locator is wrong
int matches = driver.findElements(By.cssSelector("#submit-btn")).size();
System.out.println("Matches found: " + matches); // 0 means the locator is wrong
int matches = driver.findElements(By.cssSelector("#submit-btn")).size();
System.out.println("Matches found: " + matches); // 0 means the locator is wrong

Best practices for robust locators:

  • Prefer stable IDs and names: They change less often than classes or layout.

  • Use test attributes: Ask developers to add data-testid or data-automation-id to key elements.

  • Avoid absolute XPath: It breaks whenever the page layout changes.

  • Use relative XPath or CSS: Anchor to something meaningful and stable.

  • Avoid position-based selectors: Something like (//input)[2] breaks as soon as a field is added.

// Fragile
driver.findElement(By.xpath("/html/body/div[2]/form/div[3]/button"));

// Robust
driver.findElement(By.cssSelector("[data-testid='submit-order']"));
// Fragile
driver.findElement(By.xpath("/html/body/div[2]/form/div[3]/button"));

// Robust
driver.findElement(By.cssSelector("[data-testid='submit-order']"));
// Fragile
driver.findElement(By.xpath("/html/body/div[2]/form/div[3]/button"));

// Robust
driver.findElement(By.cssSelector("[data-testid='submit-order']"));
// Fragile
driver.findElement(By.xpath("/html/body/div[2]/form/div[3]/button"));

// Robust
driver.findElement(By.cssSelector("[data-testid='submit-order']"));

Method 2: Use Explicit Waits

Many NoSuchElementException errors aren't caused by a wrong locator at all, but by looking too early. Explicit waits tell WebDriver to keep checking for a set time until the element appears.

import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

// Wait until the element exists in the DOM
WebElement table = wait.until(
    ExpectedConditions.presenceOfElementLocated(By.id("results-table"))
);

// Wait until the element is visible and clickable before using it
WebElement submit = wait.until(
    ExpectedConditions.elementToBeClickable(By.cssSelector("[data-testid='submit-order']"))
);
submit.click();
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

// Wait until the element exists in the DOM
WebElement table = wait.until(
    ExpectedConditions.presenceOfElementLocated(By.id("results-table"))
);

// Wait until the element is visible and clickable before using it
WebElement submit = wait.until(
    ExpectedConditions.elementToBeClickable(By.cssSelector("[data-testid='submit-order']"))
);
submit.click();
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

// Wait until the element exists in the DOM
WebElement table = wait.until(
    ExpectedConditions.presenceOfElementLocated(By.id("results-table"))
);

// Wait until the element is visible and clickable before using it
WebElement submit = wait.until(
    ExpectedConditions.elementToBeClickable(By.cssSelector("[data-testid='submit-order']"))
);
submit.click();
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

// Wait until the element exists in the DOM
WebElement table = wait.until(
    ExpectedConditions.presenceOfElementLocated(By.id("results-table"))
);

// Wait until the element is visible and clickable before using it
WebElement submit = wait.until(
    ExpectedConditions.elementToBeClickable(By.cssSelector("[data-testid='submit-order']"))
);
submit.click();

Common ExpectedConditions:

  • presenceOfElementLocated(): The element is in the DOM, but may not be visible

  • visibilityOfElementLocated(): The element is in the DOM and visible

  • elementToBeClickable(): The element is visible, enabled and ready to use

  • frameToBeAvailableAndSwitchToIt(): The iframe is ready, and WebDriver switches into it

Pros:Handles content that loads late, and cuts timing-related failures.

Cons: You have to add waits throughout the suite, and each one adds a little execution time.

Method 3: Set an Implicit Wait

An implicit wait sets one timeout for every element lookup. WebDriver keeps retrying for that long before throwing NoSuchElementException.

import java.time.Duration;

driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
import java.time.Duration;

driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
import java.time.Duration;

driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
import java.time.Duration;

driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));

Pros: One line applies to every lookup in the session.

Cons: It uses the same timeout for everything, can hide real locator problems behind long delays, and behaves unpredictably when mixed with explicit waits.

Best practice: Use explicit waits instead. Selenium's own documentation warns against mixing implicit and explicit waits, because it can cause unpredictable wait times.

Method 4: Add Retry Logic

Sometimes a lookup fails because of something brief, like a slow network response, an animation, or a page re-render that settles a moment later. Retrying the lookup handles these cases.

The cleanest way in Selenium is FluentWait, which retries automatically and ignores the exceptions you choose:

import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
import java.time.Duration;

Wait<WebDriver> wait = new FluentWait<>(driver)
    .withTimeout(Duration.ofSeconds(15))
    .pollingEvery(Duration.ofMillis(500))
    .ignoring(NoSuchElementException.class)
    .ignoring(StaleElementReferenceException.class);

WebElement button = wait.until(d ->
    d.findElement(By.cssSelector("[data-testid='place-order']"))
);
button.click();
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
import java.time.Duration;

Wait<WebDriver> wait = new FluentWait<>(driver)
    .withTimeout(Duration.ofSeconds(15))
    .pollingEvery(Duration.ofMillis(500))
    .ignoring(NoSuchElementException.class)
    .ignoring(StaleElementReferenceException.class);

WebElement button = wait.until(d ->
    d.findElement(By.cssSelector("[data-testid='place-order']"))
);
button.click();
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
import java.time.Duration;

Wait<WebDriver> wait = new FluentWait<>(driver)
    .withTimeout(Duration.ofSeconds(15))
    .pollingEvery(Duration.ofMillis(500))
    .ignoring(NoSuchElementException.class)
    .ignoring(StaleElementReferenceException.class);

WebElement button = wait.until(d ->
    d.findElement(By.cssSelector("[data-testid='place-order']"))
);
button.click();
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
import java.time.Duration;

Wait<WebDriver> wait = new FluentWait<>(driver)
    .withTimeout(Duration.ofSeconds(15))
    .pollingEvery(Duration.ofMillis(500))
    .ignoring(NoSuchElementException.class)
    .ignoring(StaleElementReferenceException.class);

WebElement button = wait.until(d ->
    d.findElement(By.cssSelector("[data-testid='place-order']"))
);
button.click();

If you need something more custom, a small helper method works too:

public WebElement findWithRetry(By locator, int attempts) throws InterruptedException {
    for (int i = 1; i <= attempts; i++) {
        try {
            return driver.findElement(locator);
        } catch (NoSuchElementException e) {
            if (i == attempts) {
                throw e;
            }
            Thread.sleep(500);
        }
    }
    throw new IllegalStateException("Unreachable");
}
public WebElement findWithRetry(By locator, int attempts) throws InterruptedException {
    for (int i = 1; i <= attempts; i++) {
        try {
            return driver.findElement(locator);
        } catch (NoSuchElementException e) {
            if (i == attempts) {
                throw e;
            }
            Thread.sleep(500);
        }
    }
    throw new IllegalStateException("Unreachable");
}
public WebElement findWithRetry(By locator, int attempts) throws InterruptedException {
    for (int i = 1; i <= attempts; i++) {
        try {
            return driver.findElement(locator);
        } catch (NoSuchElementException e) {
            if (i == attempts) {
                throw e;
            }
            Thread.sleep(500);
        }
    }
    throw new IllegalStateException("Unreachable");
}
public WebElement findWithRetry(By locator, int attempts) throws InterruptedException {
    for (int i = 1; i <= attempts; i++) {
        try {
            return driver.findElement(locator);
        } catch (NoSuchElementException e) {
            if (i == attempts) {
                throw e;
            }
            Thread.sleep(500);
        }
    }
    throw new IllegalStateException("Unreachable");
}

Pros: Handles brief, temporary failures, and can be reused across the suite.

Cons: Adds run time, can hide real stability problems, and means more framework code to maintain.

Method 5: Handle Iframes and Shadow DOM

If your locator is right and timing isn't the problem, the element may be inside an iframe or a shadow root. Both create separate DOM contexts that WebDriver can't reach until you step into them.

Switching into an iframe:

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

// Wait for the iframe and switch into it
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(By.id("payment-frame")));

driver.findElement(By.name("card-number")).sendKeys("4242424242424242");

// Switch back to the main page when you're done
driver.switchTo().defaultContent();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

// Wait for the iframe and switch into it
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(By.id("payment-frame")));

driver.findElement(By.name("card-number")).sendKeys("4242424242424242");

// Switch back to the main page when you're done
driver.switchTo().defaultContent();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

// Wait for the iframe and switch into it
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(By.id("payment-frame")));

driver.findElement(By.name("card-number")).sendKeys("4242424242424242");

// Switch back to the main page when you're done
driver.switchTo().defaultContent();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

// Wait for the iframe and switch into it
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(By.id("payment-frame")));

driver.findElement(By.name("card-number")).sendKeys("4242424242424242");

// Switch back to the main page when you're done
driver.switchTo().defaultContent();

Reaching elements inside a shadow root (Selenium 4):

WebElement host = driver.findElement(By.cssSelector("app-login-form"));
SearchContext shadowRoot = host.getShadowRoot();

shadowRoot.findElement(By.cssSelector("input[name='email']"))
          .sendKeys("jane@example.com");
WebElement host = driver.findElement(By.cssSelector("app-login-form"));
SearchContext shadowRoot = host.getShadowRoot();

shadowRoot.findElement(By.cssSelector("input[name='email']"))
          .sendKeys("jane@example.com");
WebElement host = driver.findElement(By.cssSelector("app-login-form"));
SearchContext shadowRoot = host.getShadowRoot();

shadowRoot.findElement(By.cssSelector("input[name='email']"))
          .sendKeys("jane@example.com");
WebElement host = driver.findElement(By.cssSelector("app-login-form"));
SearchContext shadowRoot = host.getShadowRoot();

shadowRoot.findElement(By.cssSelector("input[name='email']"))
          .sendKeys("jane@example.com");

Selenium 4's getShadowRoot() replaces the old JavaScriptExecutor workarounds. Note that only CSS selectors work inside a shadow root, so XPath won't find elements there.

Pros: Handles complex page structures properly.

Cons: You need to know how the page is built, and it adds extra steps to your tests.

Related Read: How to Handle iFrames in Selenium

Method 6: Use the Page Object Model

The Page Object Model (POM) keeps all the locators for a page in one class. It won't stop NoSuchElementException from happening, but it makes broken locators far easier to fix. When a button ID changes, you update it in one place instead of searching through dozens of test files.

public class CheckoutPage {

    private final WebDriver driver;
    private final WebDriverWait wait;

    // All locators for this page, in one place
    private final By emailField   = By.cssSelector("[data-testid='checkout-email']");
    private final By cardField    = By.cssSelector("[data-testid='card-number']");
    private final By placeOrder   = By.cssSelector("[data-testid='place-order']");
    private final By confirmation = By.cssSelector("[data-testid='order-confirmation']");

    public CheckoutPage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    public void placeOrder(String email, String card) {
        wait.until(ExpectedConditions.visibilityOfElementLocated(emailField)).sendKeys(email);
        driver.findElement(cardField).sendKeys(card);
        driver.findElement(placeOrder).click();
    }

    public String getConfirmationText() {
        return wait.until(ExpectedConditions.visibilityOfElementLocated(confirmation)).getText();
    }
}
public class CheckoutPage {

    private final WebDriver driver;
    private final WebDriverWait wait;

    // All locators for this page, in one place
    private final By emailField   = By.cssSelector("[data-testid='checkout-email']");
    private final By cardField    = By.cssSelector("[data-testid='card-number']");
    private final By placeOrder   = By.cssSelector("[data-testid='place-order']");
    private final By confirmation = By.cssSelector("[data-testid='order-confirmation']");

    public CheckoutPage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    public void placeOrder(String email, String card) {
        wait.until(ExpectedConditions.visibilityOfElementLocated(emailField)).sendKeys(email);
        driver.findElement(cardField).sendKeys(card);
        driver.findElement(placeOrder).click();
    }

    public String getConfirmationText() {
        return wait.until(ExpectedConditions.visibilityOfElementLocated(confirmation)).getText();
    }
}
public class CheckoutPage {

    private final WebDriver driver;
    private final WebDriverWait wait;

    // All locators for this page, in one place
    private final By emailField   = By.cssSelector("[data-testid='checkout-email']");
    private final By cardField    = By.cssSelector("[data-testid='card-number']");
    private final By placeOrder   = By.cssSelector("[data-testid='place-order']");
    private final By confirmation = By.cssSelector("[data-testid='order-confirmation']");

    public CheckoutPage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    public void placeOrder(String email, String card) {
        wait.until(ExpectedConditions.visibilityOfElementLocated(emailField)).sendKeys(email);
        driver.findElement(cardField).sendKeys(card);
        driver.findElement(placeOrder).click();
    }

    public String getConfirmationText() {
        return wait.until(ExpectedConditions.visibilityOfElementLocated(confirmation)).getText();
    }
}
public class CheckoutPage {

    private final WebDriver driver;
    private final WebDriverWait wait;

    // All locators for this page, in one place
    private final By emailField   = By.cssSelector("[data-testid='checkout-email']");
    private final By cardField    = By.cssSelector("[data-testid='card-number']");
    private final By placeOrder   = By.cssSelector("[data-testid='place-order']");
    private final By confirmation = By.cssSelector("[data-testid='order-confirmation']");

    public CheckoutPage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    public void placeOrder(String email, String card) {
        wait.until(ExpectedConditions.visibilityOfElementLocated(emailField)).sendKeys(email);
        driver.findElement(cardField).sendKeys(card);
        driver.findElement(placeOrder).click();
    }

    public String getConfirmationText() {
        return wait.until(ExpectedConditions.visibilityOfElementLocated(confirmation)).getText();
    }
}

Pros: Cleaner code, one place for each locator, and faster fixes.

Cons: Tests still break when locators change, and it takes effort to set up.

Method 7: Check Whether an Element Exists First

For elements that may or may not appear, like a cookie banner or an optional promotion, use findElements(). It returns an empty list instead of throwing, so you can decide what to do.

List<WebElement> cookieBanner = driver.findElements(By.id("cookie-accept"));

if (!cookieBanner.isEmpty()) {
    cookieBanner.get(0).click();
}

// Carry on with the test whether or not the banner appeared
driver.findElement(By.cssSelector("[data-testid='search-input']")).sendKeys("laptop");
List<WebElement> cookieBanner = driver.findElements(By.id("cookie-accept"));

if (!cookieBanner.isEmpty()) {
    cookieBanner.get(0).click();
}

// Carry on with the test whether or not the banner appeared
driver.findElement(By.cssSelector("[data-testid='search-input']")).sendKeys("laptop");
List<WebElement> cookieBanner = driver.findElements(By.id("cookie-accept"));

if (!cookieBanner.isEmpty()) {
    cookieBanner.get(0).click();
}

// Carry on with the test whether or not the banner appeared
driver.findElement(By.cssSelector("[data-testid='search-input']")).sendKeys("laptop");
List<WebElement> cookieBanner = driver.findElements(By.id("cookie-accept"));

if (!cookieBanner.isEmpty()) {
    cookieBanner.get(0).click();
}

// Carry on with the test whether or not the banner appeared
driver.findElement(By.cssSelector("[data-testid='search-input']")).sendKeys("laptop");

Pros: Handles optional elements cleanly, without try-catch blocks.

Cons: Doesn't fix a wrong locator, and adds conditional logic to your tests.‍

The Real Problem: Relying on a Single Locator

Why the Traditional Fixes Fall Short

Each method above helps, but none of them removes the cause:

  • Explicit waits fix timing, but not a locator that has changed.

  • Better locators break less often, but still depend on one attribute.

  • Retries hide brief failures, but don't make the test any sturdier.

  • Page objects make fixes faster, but you still have to make them.

The core issue: Selenium identifies each element through one locator, whether that's an ID, an XPath or a CSS selector. When that single reference changes, the test fails, even if the element is right there on the screen.

The Cost of Locator Maintenance

On a growing suite, locator repair quietly becomes the main job. Every release brings a batch of broken tests, someone has to work out which failures are real bugs and which are just changed locators, and new coverage waits until the fixes are done. For many teams, that's what keeps automation coverage stuck well below where they want it.

Why Modern Applications Keep Breaking Selenium Tests

  • Frequent UI changes: Agile teams change page structure every sprint.

  • Component frameworks: React, Vue and Angular re-render pages in ways that make elements harder to pin down.

  • Design system updates: Moving to a new CSS framework can change every class name at once.

  • A/B tests and feature flags: Different users see different versions of the same page.

  • Micro-frontends: Several teams ship UI changes to the same page independently.

Traditional Selenium can't adapt to any of this on its own. Every change means someone updates locators by hand.

How AI Native Testing Eliminates NoSuchElementException

  1. Finding Elements from Many Signals, Not One

AI-native test platforms don't store a single locator for each element. They record everything they can learn about it, and use whichever signals still hold when the page changes.

What changes

Traditional Selenium

Multi-signal identification

The button's ID is renamed

Test fails

Element found by its label, text and position

CSS classes are refactored

CSS selector fails

Element found by its ID, ARIA label or text

The layout is restructured

XPath fails

Element found by its label and surrounding context

The button text is reworded

Text-based XPath fails

Element found by its ID, attributes and position

Here's the same step written both ways:

// Traditional Selenium: depends entirely on one locator
driver.findElement(By.xpath("//div[@class='form-actions']/button[@id='submit-btn']")).click();
// Traditional Selenium: depends entirely on one locator
driver.findElement(By.xpath("//div[@class='form-actions']/button[@id='submit-btn']")).click();
// Traditional Selenium: depends entirely on one locator
driver.findElement(By.xpath("//div[@class='form-actions']/button[@id='submit-btn']")).click();
// Traditional Selenium: depends entirely on one locator
driver.findElement(By.xpath("//div[@class='form-actions']/button[@id='submit-btn']")).click();




  1. How the Platform Builds Its Picture of Each Element

When a test is first built, the platform scans the page and records a detailed profile of every element it interacts with:

  • Its attributes: ID, name, classes, data attributes and ARIA labels

  • Its visible text

  • Its relationships: parent, children, siblings and nearby labels

  • Its position on the page

It ranks these signals by how reliable they are. When one stops matching, the platform moves to the next, so a single renamed ID doesn't bring the test down.

  1. Plain English Tests Keep Locators Out of Sight

With natural language test authoring, testers never write XPath or CSS selectors at all:





Why this reduces NoSuchElementException:

  • No locators in the test: There's nothing hard-coded to go out of date.

  • Identification happens at run time: The platform chooses the best way to find each element as the test runs.

  • Anyone can write tests: Business analysts and manual testers can build and read them.

  • Page changes don't break the test steps: The steps describe what to do, not where the element lives.

Live Authoring adds instant feedback, too. Each step runs in a real browser as you write it, so you know straight away whether an element can be found, instead of discovering it in the next pipeline run.

  1. Self-Healing You Can Review

When an element changes, a self-healing platform finds it through its other signals and updates the test, rather than failing the run.

How it works in Virtuoso QA:

  1. A step can't find its element using the usual signals.

  2. The platform compares the page with the last successful run.

  3. It identifies the same element through its other signals.

  4. It updates the test and logs exactly what changed.

  5. Your team reviews the fix and accepts or rejects it.

That last step matters at enterprise scale. Tests keep running, but nothing changes without a record. Those records also feed into the release evidence for each run, so you can show what was tested and what was repaired.

Preventing NoSuchElementException: Best Practices

  1. If You're Staying on Selenium

Choose locators in this order, from most stable to least:

  1. Test-specific attributes like data-testid

  2. Unique, stable IDs

  3. Name attributes

  4. CSS selectors using specific, meaningful classes

  5. Relative XPath using more than one attribute

  6. Visible text, if it's unique

  7. Absolute XPath, only as a last resort

Work with your developers:

  • Add test attributes to your coding standards.

  • Ask for data-testid on every key element.

  • Review locators as part of code review.

  • Agree how UI changes get flagged to the test team.

Build a sturdy framework:

  • Keep locators in page objects.

  • Use explicit waits, not implicit ones.

  • Use FluentWait for brief, temporary failures.

  • Check optional elements with findElements().

  • Log clear failure messages that include the locator.

  1. When to Move to AI-Native Testing

Signs it's time to switch:

  • Maintenance is taking over: Your team spends more time fixing tests than writing them.

  • The pipeline fails daily on locator issues: NoSuchElementException is a regular cause of red builds.

  • Coverage has stalled: You can't grow coverage because maintenance keeps getting in the way.

  • Your app is highly dynamic: React, Vue or Angular apps where the DOM changes constantly.

  • You release often: Frequent sprints bring constant UI changes.

A practical migration path:

  1. Audit your suite. Find the tests that break most often.

  2. Build new tests on the new platform. Stop adding to the Selenium suite.

  3. Convert the worst offenders first. Virtuoso QA's GENerator converts existing Selenium scripts into plain English tests, which speeds this step up.

  4. Run both suites side by side. Keep going until the results match.

  5. Retire the old tests. Phase out Selenium as coverage moves across.

Stop Fixing Locators. Start Finding Defects.

NoSuchElementException isn't really a bug in your tests. It's a sign of an approach that was never built to handle change. Every hour spent chasing broken selectors is an hour not spent finding real defects.

Virtuoso QA tackles this at the root. Plain English tests keep locators out of your scripts. Multi-signal element identification copes when the page changes. Self-healing repairs tests and logs every change for your team to review. And every run produces release evidence, so you can show what was tested, what was repaired, and what's ready to ship.

Spend your time on defects, not locators.

Spend your time on defects, not locators.

Spend your time on defects, not locators.

Spend your time on defects, not locators.

Related Reads‍

FAQs on NoSuchElementException in Selenium

What causes NoSuchElementException in Selenium WebDriver?

How do you handle NoSuchElementException in Selenium Java?

What is the difference between NoSuchElementException and ElementNotVisibleException?

How long should WebDriverWait be for NoSuchElementException?

Why do I keep getting NoSuchElementException even with correct locators?

What is the best locator strategy to avoid NoSuchElementException?

See what your next release looks like with Virtuoso

Book a walkthrough on your applications and your workflows. Bring a requirement, a user journey, or a brittle Selenium script, and watch the loop run on something you recognise.

See what your next release looks like with Virtuoso

Book a walkthrough on your applications and your workflows. Bring a requirement, a user journey, or a brittle Selenium script, and watch the loop run on something you recognise.

See what your next release looks like with Virtuoso

Book a walkthrough on your applications and your workflows. Bring a requirement, a user journey, or a brittle Selenium script, and watch the loop run on something you recognise.

Virtuoso QA is establishing the standard of proof for software releases. Its governed QA loop turns business requirements into tests for any browser-based application: AI proposes, a deterministic engine executes, a person approves what matters, and every decision leaves evidence.

Trust Center

AICPA

SOC

WAVE STRONG PERFORMER

@ Copyright 2026 SpotQA, Creators of Virtuoso QA

Virtuoso QA is establishing the standard of proof for software releases. Its governed QA loop turns business requirements into tests for any browser-based application: AI proposes, a deterministic engine executes, a person approves what matters, and every decision leaves evidence.

Trust Center

AICPA

SOC

WAVE STRONG PERFORMER

@ Copyright 2026 SpotQA, Creators of Virtuoso QA

Virtuoso QA is establishing the standard of proof for software releases. Its governed QA loop turns business requirements into tests for any browser-based application: AI proposes, a deterministic engine executes, a person approves what matters, and every decision leaves evidence.

Trust Center

AICPA

SOC

WAVE STRONG PERFORMER

@ Copyright 2026 SpotQA, Creators of Virtuoso QA