You run the test. The page looks right in the browser. Selenium disagrees:
NoSuchElementException: no such element: Unable to locate element:
{"method":"css selector","selector":"[data-testid='save-button']"}
You open the page yourself, and the button is right there.
Almost every time, this error does not mean what people think it means. It rarely means the element is missing from the application. It means the element was not there at the moment Selenium looked. Those are different problems, and the difference is the whole fix.
What the error actually says
findElement does one thing: it looks in the page once, right now. If nothing matches, it throws immediately.
There is no waiting built in. No retrying. Selenium asks the browser a question, gets "nothing found", and gives up. This is the single most important thing to understand about it, because it explains four of the five causes below.
The five causes, most common first
1. The element is not there yet
This is the big one. Modern pages load content after the first render: a network call finishes, a framework re-renders, an animation completes. Your test asked too early.
await driver.get('https://example.com/contacts');
// The list is still loading. This throws.
await driver.findElement(By.css('[data-testid="contact-row"]'));
The page looks fine when you open it by hand, because by the time your eyes reach the screen, a second has passed. Your test does not wait a second.
2. Your selector is wrong
Ordinary and worth checking early. A typo, a renamed attribute, or a class that changed in a redesign.
Confirm it in the browser console before blaming timing:
document.querySelectorAll('[data-testid="save-button"]').length
If that returns 0 with the page fully loaded, the selector is the problem, not the wait.
3. The element is inside an iframe
An iframe is a separate document. Selenium looks in the current document only, so an element inside a frame is genuinely not there until you switch into it.
const frame = await driver.findElement(By.css('iframe#payment'));
await driver.switchTo().frame(frame);
await driver.findElement(By.css('[name="cardnumber"]')); // now it resolves
await driver.switchTo().defaultContent();
If a card field or a chat widget throws this error, suspect a frame first. We go deeper in handling iframes.
4. You are on the wrong page
A redirect fired, a login expired, or the previous step failed quietly and left you somewhere else. The test keeps going and looks for an element that was never on this page.
Print where you actually are:
console.log(await driver.getCurrentUrl());
console.log(await driver.getTitle());
Two seconds of checking saves an hour of selector tuning.
5. The element is in the DOM but only after an action
A menu item that exists only once the menu opens. A row that appears only after a search. The element is not there because the thing that creates it has not happened yet.
The fix: wait for the element, do not sleep
The correct answer for cause 1 is an explicit wait. You tell Selenium what to wait for, and it keeps checking until it appears or the time runs out.
// Java
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement save = wait.until(
ExpectedConditions.presenceOfElementLocated(By.cssSelector("[data-testid='save-button']")));
save.click();
// JavaScript
const save = await driver.wait(
until.elementLocated(By.css('[data-testid="save-button"]')),
10000
);
await save.click();
Pick the right condition, because "present" is not the same as "usable".
presenceOfElementLocatedwaits until it exists in the DOM. It may still be invisible.visibilityOfElementLocatedwaits until it exists and can be seen.elementToBeClickablewaits until it is visible and enabled.
If you wait for presence and then click, you can trade this error for ElementNotInteractableException. Wait for what you actually need to do.
Do not fix it with a sleep
await driver.sleep(3000); // please do not
A fixed sleep is wrong in both directions. It wastes three seconds when the page was ready in 200ms, and it still fails on the day the server is slow. Multiply that across a suite and you have a run that is both slow and unreliable.
An explicit wait continues the moment the element appears, and only fails if it genuinely never does.
What about implicit waits?
An implicit wait tells the driver to keep retrying every findElement for a set time. It looks like a one-line fix for the whole problem:
await driver.manage().setTimeouts({ implicit: 10000 });
It works, and most experienced teams still avoid it. Two reasons.
It only waits for presence, so it does nothing about visible or clickable, which is what you usually need. And mixing implicit and explicit waits produces unpredictable timing, because the two mechanisms interact in ways that are hard to reason about. The Selenium documentation warns against combining them.
Pick one approach. Explicit waits are more typing and far more predictable, which is why they win on real suites.
When you expect the element to be absent
If you are checking that something is not there, do not catch the exception. Use findElements, which returns a list and never throws:
const errors = await driver.findElements(By.css('.error-message'));
assert.strictEqual(errors.length, 0, 'no error should be shown');
This is the correct way to assert absence. Wrapping findElement in a try/catch to prove a negative is slower and reads worse.
The debugging order that saves time
When you hit this error, work through it in this order. It goes from cheapest to most expensive:
- Is the selector right? Check it in the browser console on the real page.
- Where am I? Print the URL and title.
- Is it in a frame? Look at the element in developer tools and check for an
<iframe>above it. - Is it a timing problem? Add an explicit wait for the condition you need.
- Does it require an action first? Open the menu, run the search, then look.
Most people start at step 4 and add a longer sleep. Starting at step 1 finds the real cause far more often.
Remember this
findElement looks once and throws immediately, so this error usually means "not there yet", not "does not exist". Check the selector and the URL first, rule out an iframe, then wait explicitly for the condition you actually need. Use findElements when you want to prove something is absent, and keep fixed sleeps out of your suite.
The Selenium track goes deeper on waiting, which is where most of these failures really begin: waits in depth.
Related: element not interactable and stale element reference, the two errors you meet next.