---
title: Your first Selenium test with TestNG | TestingBot
description: Time to drive a real browser. We will test the web form that the Selenium
  project publishes for exactly this purpose, at…
source_url:
  html: https://testingbot.com/resources/courses/selenium-testng/first-test
  md: https://testingbot.com/resources/courses/selenium-testng/first-test.md
---

Course

[Selenium Java Tutorial with TestNG](https://testingbot.com/resources/courses/selenium-testng)

Chapter 3 of 11

1. [1 Selenium and TestNG: how they fit together](https://testingbot.com/resources/courses/selenium-testng)
2. [2 Set up a Maven project for Selenium](https://testingbot.com/resources/courses/selenium-testng/project-setup)
3. [3 Your first Selenium test with TestNG](https://testingbot.com/resources/courses/selenium-testng/first-test)
4. [4 TestNG annotations and the driver lifecycle](https://testingbot.com/resources/courses/selenium-testng/annotations)
5. [5 Hard assertions vs SoftAssert](https://testingbot.com/resources/courses/selenium-testng/assertions)
6. [6 Data-driven tests with DataProvider](https://testingbot.com/resources/courses/selenium-testng/data-providers)
7. [7 Suites, groups and parameters in testng.xml](https://testingbot.com/resources/courses/selenium-testng/testng-xml)
8. [8 Run Selenium tests in parallel](https://testingbot.com/resources/courses/selenium-testng/parallel-execution)
9. [9 Listeners, screenshots and reports](https://testingbot.com/resources/courses/selenium-testng/listeners-and-reports)
10. [10 Run the suite on a Selenium grid](https://testingbot.com/resources/courses/selenium-testng/run-on-a-selenium-grid)
11. [11 Knowledge check](https://testingbot.com/resources/courses/selenium-testng/knowledge-check)

[Exit course](https://testingbot.com/resources/courses)

 Chapter 3 of 11 Intermediate 7 min read 3 quick checks Updated Nov 2026 

# Your first Selenium test with TestNG
On this page
1. [The test class](https://testingbot.com#the-test-class)
2. [What each part does](https://testingbot.com#what-each-part-does)
3. [Explicit waits](https://testingbot.com#explicit-waits)
4. [Headless runs and window size](https://testingbot.com#headless-runs-and-window-size)
5. [Reading a failure](https://testingbot.com#reading-a-failure)
6. [Running a single test](https://testingbot.com#running-a-single-test)

Time to drive a real browser. We will test the web form that the Selenium project publishes for exactly this purpose, at `https://www.selenium.dev/selenium/web/web-form.html`, and a second page that adds elements after a delay, so we can see why waiting matters.

## The test class[#](https://testingbot.com#the-test-class)

Replace `FirstTest.java` with:

```java
package com.example;

import java.time.Duration;
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 org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class FirstTest {

    private WebDriver driver;
    private WebDriverWait wait;

    @BeforeMethod
    public void startBrowser() {
        driver = new ChromeDriver();
        wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    @Test
    public void submitsTheWebForm() {
        driver.get("https://www.selenium.dev/selenium/web/web-form.html");
        Assert.assertEquals(driver.getTitle(), "Web form");

        driver.findElement(By.id("my-text-id")).sendKeys("Selenium");
        driver.findElement(By.name("my-password")).sendKeys("secret");
        driver.findElement(By.cssSelector("button[type='submit']")).click();

        WebElement message = wait.until(
                ExpectedConditions.visibilityOfElementLocated(By.id("message")));
        Assert.assertEquals(message.getText(), "Received!");
    }

    @Test
    public void waitsForADelayedElement() {
        driver.get("https://www.selenium.dev/selenium/web/dynamic.html");
        driver.findElement(By.id("adder")).click();

        // The page adds the box after a 1 second setTimeout
        WebElement box = wait.until(
                ExpectedConditions.presenceOfElementLocated(By.id("box0")));
        Assert.assertEquals(box.getDomAttribute("class"), "redbox");
    }

    @AfterMethod(alwaysRun = true)
    public void quitBrowser() {
        if (driver != null) {
            driver.quit();
        }
    }
}
```

Run it with `mvn test`. Chrome opens twice (once per test method), and you should see `Tests run: 2, Failures: 0`.

## What each part does[#](https://testingbot.com#what-each-part-does)

**`@BeforeMethod` / `@AfterMethod`** run around every `@Test` method, so each test gets a fresh browser with no cookies or state left over from the previous one. `alwaysRun = true` on the teardown means it also runs when the test fails, which is what prevents orphaned Chrome processes piling up on your machine. Chapter 3 looks at the full set of lifecycle annotations.

**`driver.get(url)`** navigates and blocks until the page's load event fires. It does **not** wait for JavaScript that runs afterwards, such as content rendered by a framework or fetched with `fetch()`.

**Locators.** `By.id`, `By.name` and `By.cssSelector` are the ones you will use most. Prefer IDs, names or dedicated test attributes (`[data-testid='submit']`) over long CSS chains or XPath that mirror the page layout: layout changes far more often than IDs do. Once a page is used by more than one test, wrap its locators in a page object; the [Page Object Model course](https://testingbot.com/resources/courses/page-object-model) covers that pattern, so we will keep locators inline here.

**`findElement` vs `findElements`.** `findElement` throws `NoSuchElementException` when nothing matches. `findElements` returns an empty list instead, which is the right tool for asserting that something is _absent_.

You need to assert that an error banner is not on the page. Which approach fits?

 `driver.findElements(By.id("error")).isEmpty()` `driver.findElement(By.id("error"))` inside a `try`/`catch`, passing on the exception `Assert.assertNull(driver.findElement(By.id("error")))`

`findElements` returns an empty list when nothing matches. `findElement` never returns null; it throws, so asserting null on it cannot pass.

## Explicit waits[#](https://testingbot.com#explicit-waits)

The second test is the important one. The `dynamic.html` page appends `#box0` one second after you click the button. Without a wait, `findElement(By.id("box0"))` runs immediately, finds nothing and throws.

`WebDriverWait` polls a condition until it returns something non-null (or `true`), or the timeout expires, in which case it throws `TimeoutException`. The `ExpectedConditions` class has ready-made conditions:

| Condition | Returns when |
| --- | --- |
| `presenceOfElementLocated(by)` | the element is in the DOM (may be hidden) |
| `visibilityOfElementLocated(by)` | the element is in the DOM and displayed |
| `elementToBeClickable(by)` | visible and enabled |
| `textToBePresentInElementLocated(by, text)` | the element's text contains `text` |
| `invisibilityOfElementLocated(by)` | the element is gone or hidden (spinners) |
| `urlContains(fragment)` | the current URL contains `fragment` |

You can also pass a lambda when none of them fit:

```java
String status = wait.until(d -> {
    String text = d.findElement(By.id("status")).getText();
    return text.isEmpty() ? null : text; // null means "keep polling"
});
```

Three rules keep waits from becoming a source of flakiness:

1. **Wait for the condition the next line depends on** , not for a duration. `Thread.sleep(2000)` is too long on a fast machine and too short on a loaded CI agent.
2. **Don't mix implicit and explicit waits.** An implicit wait (`driver.manage().timeouts().implicitlyWait(...)`) applies to every `findElement` call, including the ones inside `WebDriverWait`. The Selenium docs warn that combining a 10-second implicit wait with a 15-second explicit wait can time out after 20 seconds. Pick explicit waits and leave the implicit wait at its default of 0.
3. **Don't "fix" an intermittent failure by raising the timeout.** Find out which condition was not yet true and wait for that instead.

The suite sets a 10 second implicit wait and also uses 15 second explicit waits. Some failures take more than 20 seconds to report. What is the fix?

 Raise the explicit waits to 30 seconds Lower the implicit wait to 5 seconds Replace explicit waits with `Thread.sleep` Use explicit waits only and leave the implicit wait at its default of 0

The implicit wait applies to every `findElement`, including those inside `WebDriverWait`, so the two combine unpredictably. Pick explicit waits.

## Headless runs and window size[#](https://testingbot.com#headless-runs-and-window-size)

On a CI server you will usually run Chrome headless:

```java
import org.openqa.selenium.chrome.ChromeOptions;

ChromeOptions options = new ChromeOptions();
options.addArguments("--headless=new", "--window-size=1920,1080");
driver = new ChromeDriver(options);
```

Set the window size explicitly. When we ran this exact form test headless without it, the default headless window was small enough that another element overlapped the submit button, and the click failed with `ElementClickInterceptedException`. A headed browser on a large monitor never shows the problem, which makes it a classic "passes locally, fails in CI" bug. If you do hit that exception, [this guide to ElementClickInterceptedException](https://testingbot.com/resources/articles/selenium-elementclickinterceptedexception) walks through the other causes.

A test passes in a headed browser on your monitor but fails on CI in headless Chrome with `ElementClickInterceptedException`. What should you check first?

 Whether the headless window size is set explicitly, for example `--window-size=1920,1080` Whether CI has a newer Selenium Whether the click needs a longer implicit wait

The default headless window is small, so other elements can overlap the target. A fixed window size makes local and CI layouts match.

## Reading a failure[#](https://testingbot.com#reading-a-failure)

Change the expected title to `"Web form!"` and run again. TestNG reports:

```
java.lang.AssertionError: expected [Web form!] but found [Web form]
```

The message format is `expected [<second argument>] but found [<first argument>]`. In TestNG, `assertEquals(actual, expected)` takes the **actual value first** , the opposite of JUnit. Swap them and your failure messages will lie to you; chapter 4 has more on assertions.

Exceptions thrown by Selenium itself (`NoSuchElementException`, `TimeoutException`, `StaleElementReferenceException`) also fail the test, with the locator in the message. Our article on [exception handling with Selenium](https://testingbot.com/resources/articles/exception-handling-with-selenium) explains what each one usually means.

## Running a single test[#](https://testingbot.com#running-a-single-test)

While you are working on one test, run just that method:

```bash
mvn test -Dtest=FirstTest#waitsForADelayedElement
```

or use the green arrow next to the method in IntelliJ IDEA or Eclipse (with the TestNG plugin).

Our test class now has its setup and teardown copied into it. Add a second class and you would copy them again. The next chapter moves that lifecycle into a base class and explains where each TestNG annotation fits.

[Previous · Chapter 2 Set up a Maven project for Selenium](https://testingbot.com/resources/courses/selenium-testng/project-setup) [Next · Chapter 4 TestNG annotations and the driver lifecycle](https://testingbot.com/resources/courses/selenium-testng/annotations)

On this page

1. [The test class](https://testingbot.com#the-test-class)
2. [What each part does](https://testingbot.com#what-each-part-does)
3. [Explicit waits](https://testingbot.com#explicit-waits)
4. [Headless runs and window size](https://testingbot.com#headless-runs-and-window-size)
5. [Reading a failure](https://testingbot.com#reading-a-failure)
6. [Running a single test](https://testingbot.com#running-a-single-test)

Certification

This course has a free, hands-on certificate.

[How it works](https://testingbot.com/resources/courses/selenium-testng/certification)
