Skip to main content
Back to blog
QATest Automation

Automated Testing with Selenium

Caio Duarte· PartnerOctober 7, 2025·5 min read
Automated Testing with Selenium

Selenium has been, for more than two decades, one of the most widely used tools for test automation of web applications. It controls the browser like a real user, which makes it possible to validate complete end-to-end flows. With Selenium 4, the tool gained a standardized protocol, automatic driver management, and new features that make writing tests simpler and more stable.

This guide is practical and technical. We will go from the environment to the first test with JUnit 5, covering waits, locators, the Page Object Model (POM) architecture, integration with Cucumber, and a look at the future with Vibium. The examples use Selenium 4 and Java.

What is Selenium?

Selenium is not a single tool, but a suite. Its three main components are WebDriver, which controls the browser via code; Selenium IDE, an extension to quickly record and play back tests; and Selenium Grid, which distributes execution in parallel across several machines and browsers. In serious automation, WebDriver is the protagonist.

What changed in Selenium 4

If you learned Selenium years ago, it is worth knowing what changed, because some of the old content on the internet has become outdated:

  • W3C WebDriver protocol: communication with the browser now follows the W3C standard, which made tests more stable across browsers.
  • Selenium Manager: Selenium downloads and configures the browser driver automatically. You no longer need to download chromedriver manually or use external libraries for that.
  • Relative locators: new locators that find elements by their position relative to others (above, below, to the left, to the right, or near).
  • New window and tab API: opening a tab or window became straightforward, with driver.switchTo().newWindow().
  • WebDriver BiDi: a bidirectional protocol that paves the way for intercepting network traffic, capturing console logs, and more advanced scenarios.

Preparing the environment

Here is the first piece of good news: you do not need to download a specific IDE or the browser driver manually. Any modern editor works, and Selenium Manager takes care of the driver. In a Java project with Maven, you just add the Selenium and JUnit 5 dependencies:

XML
<!-- pom.xml -->
<dependencies>
  <dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>4.27.0</version>
  </dependency>
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.11.3</version>
    <scope>test</scope>
  </dependency>
</dependencies>

Always use the latest available version. With that, the environment is ready for the first test.

First test with Selenium 4 and JUnit 5

The example below automates a login and validates the redirect. Note the use of @BeforeEach and @AfterEach to open and close the browser for each test, keeping isolation:

Java
import org.junit.jupiter.api.*;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import static org.junit.jupiter.api.Assertions.assertTrue;

class LoginTest {

    WebDriver driver;

    @BeforeEach
    void setUp() {
        // Selenium Manager downloads and configures the driver automatically
        driver = new ChromeDriver();
        driver.manage().window().maximize();
    }

    @Test
    void shouldAuthenticateWithValidCredentials() {
        driver.get("https://example.com/login");
        driver.findElement(By.id("email")).sendKeys("user@example.com");
        driver.findElement(By.id("password")).sendKeys("password123");
        driver.findElement(By.cssSelector("button[type=submit]")).click();

        assertTrue(driver.getCurrentUrl().contains("/dashboard"));
    }

    @AfterEach
    void tearDown() {
        driver.quit();
    }
}

Waits: the secret against flaky tests

The biggest cause of flaky tests is interacting with an element before it is ready. Avoid Thread.sleep with a fixed time and prefer explicit waits, which wait for a specific condition:

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

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.elementToBeClickable(By.id("submit"))).click();

Locators and the new relative locators

Besides the classic locators (By.id, By.cssSelector, By.xpath), Selenium 4 brings relative locators, which find elements by their position relative to others. They are useful when the HTML structure is unstable but the visual layout is predictable:

Java
import static org.openqa.selenium.support.locators.RelativeLocator.with;

// Finds the password field located right below the email field
WebElement password = driver.findElement(
    with(By.tagName("input")).below(By.id("email")));

Organizing tests with the Page Object Model (POM)

Writing locators scattered across tests becomes a maintenance nightmare. The Page Object Model solves this by representing each page as a class that concentrates its elements and actions. When the interface changes, the adjustment happens in a single place. It is one of the most important practices for a sustainable suite, a topic we explore further in the article on automation frameworks.

First, the login page as a Page Object:

Java
public class LoginPage {

    private final WebDriver driver;
    private final By email = By.id("email");
    private final By password = By.id("password");
    private final By submit = By.cssSelector("button[type=submit]");

    public LoginPage(WebDriver driver) {
        this.driver = driver;
    }

    public DashboardPage authenticate(String user, String pass) {
        driver.findElement(email).sendKeys(user);
        driver.findElement(password).sendKeys(pass);
        driver.findElement(submit).click();
        return new DashboardPage(driver);
    }
}

Now the test is clean, readable, and focused on behavior, with no locator details:

Java
@Test
void validLoginLeadsToDashboard() {
    driver.get("https://example.com/login");

    DashboardPage dashboard = new LoginPage(driver)
        .authenticate("user@example.com", "password123");

    assertTrue(dashboard.isVisible());
}

Selenium with Cucumber (BDD)

A strong point of Selenium is its flexibility: it fits both with test frameworks like JUnit 5 and with BDD approaches like Cucumber. In BDD, the scenario is described in near-natural language, in the Given-When-Then format, bringing the technical team and the business closer together. See the same login described in a feature file:

Texto / outro
# login.feature
Feature: Login
  Scenario: Login with valid credentials
    Given I am on the login page
    When I enter a valid username and password
    Then I should be redirected to the dashboard

The steps link each phrase to the Selenium code. Notice that the same Page Object is reused, showing how POM, Selenium, and Cucumber work well together:

Java
import io.cucumber.java.en.*;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class LoginSteps {

    WebDriver driver = new ChromeDriver();
    LoginPage login = new LoginPage(driver);

    @Given("I am on the login page")
    public void openLogin() {
        driver.get("https://example.com/login");
    }

    @When("I enter a valid username and password")
    public void enterCredentials() {
        login.authenticate("user@example.com", "password123");
    }

    @Then("I should be redirected to the dashboard")
    public void validateDashboard() {
        assertTrue(driver.getCurrentUrl().contains("/dashboard"));
    }
}

Best practices with Selenium

  • Adopt the Page Object Model from the start to isolate the interface from the test logic.
  • Use explicit waits and avoid fixed wait times.
  • Keep test data isolated and prioritize scenarios by risk, as in regression testing.
  • Integrate the suite into the CI/CD pipeline to run on every delivery.
  • Scale execution in parallel with Selenium Grid when the suite grows.
  • Watch out for practices that sabotage results, as we detail in automation best practices.

The future: from Selenium to Vibium

It is worth keeping an eye on what is coming. Jason Huggins, the creator of Selenium, is developing Vibium, presented as a successor designed for the AI era. The idea is born from a direct question: if we were to rebuild Selenium so that it stopped being flaky, what would we change? The answer involves communication via WebSocket and WebDriver BiDi, along with features such as describing tests in natural language and self-healing of broken selectors.

Vibium is still in an early stage, but it signals where automation is heading. Knowing Selenium well today is the best foundation for following this evolution without surprises.

Conclusion

Selenium 4 is simpler and more stable than the versions that made the tool popular. With Selenium Manager, relative locators, and the W3C protocol, writing tests became more straightforward, and, combined with good practices like the Page Object Model, JUnit 5, and Cucumber, it serves everyone from technical teams to BDD scenarios. To choose the right tool for your context, it is worth comparing the options in our guide on test automation frameworks.

It is worth noting that Proton, Atomic's automation platform, supports Selenium. This makes it possible to integrate and orchestrate the automations built with this technology in a single place, adding the power of Selenium to the platform's centralized management.

Do you want to implement test automation with the confidence of a company with 10 years in the market and expertise in Quality Assurance? Talk to Atomic Solutions and speed up your deliveries without giving up quality.

Keep reading