TestKase
TestKase
|Docs
Test ManagementAutomationTest Frameworks

Selenium

Set up Selenium WebDriver for browser testing and integrate test results with TestKase.

Overview

Selenium is an open-source browser automation framework that supports multiple programming languages including Java, Python, JavaScript, and C#. It uses the WebDriver protocol to control browsers programmatically, making it the most widely adopted tool for end-to-end web testing.

Selenium itself does not produce test reports — it relies on the test runner you pair it with (JUnit, TestNG, pytest, etc.). To integrate with TestKase, use:

  • --format junit when running Selenium tests with JUnit (Java) or pytest (Python)
  • --format testng when running Selenium tests with TestNG (Java)

Prerequisites

  • Java: Java 11 or later, Maven 3.6+
  • Python: Python 3.8 or later, pip
  • Node.js: Node.js 18 or later (for JavaScript bindings)
  • Browser drivers: ChromeDriver, GeckoDriver (Firefox), or EdgeDriver matching your installed browser version
  • A TestKase account with a project, test cycle, and Automation IDs configured on your test cases

Installation

Java (Maven)

Add the following dependencies to your pom.xml:

<dependencies>
    <!-- Selenium WebDriver -->
    <dependency>
        <groupId>org.seleniumhq.selenium</groupId>
        <artifactId>selenium-java</artifactId>
        <version>4.18.1</version>
    </dependency>

    <!-- JUnit 5 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.2</version>
        <scope>test</scope>
    </dependency>

    <!-- WebDriverManager (auto-manages browser drivers) -->
    <dependency>
        <groupId>io.github.bonigarcia</groupId>
        <artifactId>webdrivermanager</artifactId>
        <version>5.7.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Python

pip install selenium pytest

Project Setup

Java — Maven Project Structure

my-selenium-project/
├── pom.xml
└── src/
    └── test/
        └── java/
            └── com/
                └── example/
                    ├── LoginTest.java
                    └── SearchTest.java

Your pom.xml should include the Maven Surefire Plugin (included by default in most Maven projects) which generates JUnit XML reports in target/surefire-reports/.

A bare Surefire declaration will not carry your @DisplayName into the XML. By default Surefire writes the method name into the <testcase name="..."> attribute and ignores @DisplayName entirely — the phrased-name options default to false. The reporter reads only that name attribute, so with a bare Surefire plugin block it extracts zero Automation IDs, prints N tests have no automation ID in their name, and exits 0 — a green build that reported nothing.

Pick one of the two working approaches below before you wire this into CI.

Option A (recommended) — turn on Surefire's phrased names, so @DisplayName becomes the <testcase name> and the default [XXXXX] bracket pattern works:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.2.5</version>
            <configuration>
                <statelessTestsetReporter
                    implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
                    <usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
                </statelessTestsetReporter>
            </configuration>
        </plugin>
    </plugins>
</build>

Option B — put the ID in the method name and leave Surefire alone. Name the method test_48271_validLogin() and report with --automation-id-format "_(\\d{5})_", exactly as the TestNG and NUnit guides do.

Either way, confirm the IDs are actually being extracted with --dry-run --verbose before enabling real reporting.

The examples on this page use Option A.

Python — Project Structure

my-selenium-project/
├── requirements.txt
├── conftest.py
└── tests/
    ├── test_login.py
    └── test_search.py

requirements.txt:

selenium>=4.18.0
pytest>=8.0.0

Writing Tests

Java — JUnit 5 Test Class

package com.example;

import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.jupiter.api.*;
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 java.time.Duration;

import static org.junit.jupiter.api.Assertions.*;

public class LoginTest {

    private WebDriver driver;
    private WebDriverWait wait;

    @BeforeEach
    void setUp() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        driver.get("https://example.com/login");
    }

    @AfterEach
    void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }

    @Test
    @DisplayName("[48271] testValidLogin")
    void testValidLogin() {
        driver.findElement(By.id("username")).sendKeys("testuser");
        driver.findElement(By.id("password")).sendKeys("password123");
        driver.findElement(By.id("login-btn")).click();

        WebElement dashboard = wait.until(
            ExpectedConditions.presenceOfElementLocated(By.id("dashboard"))
        );
        assertTrue(dashboard.isDisplayed());
    }

    @Test
    @DisplayName("[48272] testInvalidLogin")
    void testInvalidLogin() {
        driver.findElement(By.id("username")).sendKeys("invalid");
        driver.findElement(By.id("password")).sendKeys("wrong");
        driver.findElement(By.id("login-btn")).click();

        WebElement error = wait.until(
            ExpectedConditions.presenceOfElementLocated(By.className("error-message"))
        );
        assertEquals("Invalid credentials", error.getText());
    }
}

Each test includes a 5-digit Automation ID in its @DisplayName annotation using square brackets. The @testkase/reporter CLI extracts these IDs using the regex \[(\d{5})\] — but only if Surefire is configured with usePhrasedTestCaseMethodName as shown in Option A above. Without it the XML carries testValidLogin, not [48271] testValidLogin, and nothing is matched. For the tests above:

  • 48271 → linked to the "valid login" test case in TestKase
  • 48272 → linked to the "invalid login" test case in TestKase

Python — pytest Test File

import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class TestLogin:
    def setup_method(self):
        self.driver = webdriver.Chrome()
        self.driver.get("https://example.com/login")
        self.wait = WebDriverWait(self.driver, 10)

    def teardown_method(self):
        if self.driver:
            self.driver.quit()

    @pytest.mark.parametrize("_id", [pytest.param("48271", id="[48271]")])
    def test_valid_login(self, _id):
        self.driver.find_element(By.ID, "username").send_keys("testuser")
        self.driver.find_element(By.ID, "password").send_keys("password123")
        self.driver.find_element(By.ID, "login-btn").click()

        dashboard = self.wait.until(
            EC.presence_of_element_located((By.ID, "dashboard"))
        )
        assert dashboard.is_displayed()

    @pytest.mark.parametrize("_id", [pytest.param("48272", id="[48272]")])
    def test_invalid_login(self, _id):
        self.driver.find_element(By.ID, "username").send_keys("invalid")
        self.driver.find_element(By.ID, "password").send_keys("wrong")
        self.driver.find_element(By.ID, "login-btn").click()

        error = self.wait.until(
            EC.presence_of_element_located((By.CLASS_NAME, "error-message"))
        )
        assert error.text == "Invalid credentials"

Embed 5-digit Automation IDs in a pytest.param(..., id="[XXXXX]") parametrize ID so they appear in the test's name in the JUnit XML (e.g. test_valid_login[[48271]]). pytest does not write docstrings or @pytest.mark labels to the JUnit name, so IDs placed there will not match:

  • [48271] → linked to the "valid login" test case in TestKase
  • [48272] → linked to the "invalid login" test case in TestKase

See the pytest guide for full details on tagging parametrized and non-parametrized tests.

Running Tests

Java (Maven)

mvn test

JUnit XML reports are generated automatically in target/surefire-reports/. Each test class produces a file named TEST-<className>.xml (e.g., TEST-com.example.LoginTest.xml).

Python (pytest)

pytest --junitxml=test-results/junit.xml

This produces a single JUnit XML file at test-results/junit.xml.

TestKase Integration

After running your tests and generating the results file, use the @testkase/reporter CLI to push results to TestKase.

Java (JUnit XML from Maven Surefire)

npx @testkase/reporter report \
  --token $TESTKASE_PAT \
  --project-id PRJ-1 \
  --org-id 1173 \
  --cycle-id TCYCLE-5 \
  --format junit \
  --results-file "target/surefire-reports/TEST-*.xml"

--cycle-id is optional. If not provided, results are reported to TCYCLE-1 — the master test cycle for the project.

Python (JUnit XML from pytest)

npx @testkase/reporter report \
  --token $TESTKASE_PAT \
  --project-id PRJ-1 \
  --org-id 1173 \
  --cycle-id TCYCLE-5 \
  --format junit \
  --results-file test-results/junit.xml

Use --dry-run --verbose the first time to verify that your test names match the Automation IDs you set in TestKase. This helps catch naming mismatches before pushing real results.

Automation ID Mapping

The reporter extracts 5-digit Automation IDs from test names using the [XXXXX] bracket pattern:

RunnerWhere to Embed the IDExample
Java (JUnit 5)@DisplayName annotation — requires Surefire's usePhrasedTestCaseMethodName (Option A above)@DisplayName("[48271] testValidLogin")
Java (JUnit 5), no Surefire configTest method name + --automation-id-format "_(\\d{5})_"void test_48271_validLogin()
Java (TestNG, --format testng)Test method name + --automation-id-format "_(\\d{5})_"public void test_48271_validLogin()
Python (pytest)pytest.param IDpytest.param(..., id="[48271]")
JavaScript (Mocha/Jest)it() or test() nametest('[48271] should authenticate user', ...)

Generate Automation IDs in TestKase first, then embed them in your test names. Use --dry-run to verify which [XXXXX] IDs the reporter extracts from your results file.

Complete Example

This end-to-end example shows the full flow for a Java Selenium project with JUnit 5.

1. Create the Test

package com.example;

import io.github.bonigarcia.wdm.WebDriverManager;
import org.junit.jupiter.api.*;
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 java.time.Duration;

import static org.junit.jupiter.api.Assertions.*;

public class SearchTest {

    private WebDriver driver;
    private WebDriverWait wait;

    @BeforeEach
    void setUp() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

    @AfterEach
    void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }

    @Test
    @DisplayName("[48271] testSearchReturnsResults")
    void testSearchReturnsResults() {
        driver.get("https://example.com");
        driver.findElement(By.id("search-input")).sendKeys("selenium");
        driver.findElement(By.id("search-btn")).click();

        WebElement results = wait.until(
            ExpectedConditions.presenceOfElementLocated(By.id("search-results"))
        );
        assertTrue(results.isDisplayed());
    }
}

2. Set the Automation ID in TestKase

In your TestKase project, generate an Automation ID (e.g., 48271) on the corresponding test case, then embed it in your test's @DisplayName annotation: @DisplayName("[48271] testSearchReturnsResults")

This example assumes the Option A Surefire configuration from the Project Setup section above. Without it, Surefire writes testSearchReturnsResults into the XML and no ID is extracted.

3. Run the Tests

mvn test

4. Verify with Dry Run

npx @testkase/reporter report \
  --token $TESTKASE_PAT \
  --project-id PRJ-1 \
  --org-id 1173 \
  --cycle-id TCYCLE-5 \
  --format junit \
  --results-file "target/surefire-reports/TEST-com.example.SearchTest.xml" \
  --dry-run --verbose

5. Push Results

Remove the --dry-run flag to push results to TestKase:

npx @testkase/reporter report \
  --token $TESTKASE_PAT \
  --project-id PRJ-1 \
  --org-id 1173 \
  --cycle-id TCYCLE-5 \
  --format junit \
  --results-file "target/surefire-reports/TEST-com.example.SearchTest.xml"

Troubleshooting

WebDriver not found

org.openqa.selenium.SessionNotCreatedException: Could not start a new session.

Cause: The browser driver (ChromeDriver, GeckoDriver) is not installed or not on your PATH.

Fix: Use WebDriverManager to automatically download and configure the correct driver version:

// Add to your test setup:
WebDriverManager.chromedriver().setup();

Or install drivers manually and add them to your system PATH.

No Automation IDs extracted from a Java run

0 tests have an automation ID in their name

Cause: Surefire wrote the method name into the JUnit XML instead of your @DisplayName. The phrased-name options default to false, so a bare maven-surefire-plugin block produces <testcase name="testValidLogin"> and the [48271] in @DisplayName never reaches the file.

Check: open the generated XML and look at the name attribute:

grep -o 'testcase name="[^"]*"' target/surefire-reports/TEST-com.example.LoginTest.xml

Fix: add the usePhrasedTestCaseMethodName configuration shown in Option A under Project Setup, or switch to Option B and put the ID in the method name with --automation-id-format "_(\\d{5})_".

Stale element references

org.openqa.selenium.StaleElementReferenceException: stale element reference

Cause: The DOM element was modified or removed after you located it.

Fix: Use WebDriverWait with ExpectedConditions to wait for elements to be present or visible before interacting with them:

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement element = wait.until(
    ExpectedConditions.presenceOfElementLocated(By.id("my-element"))
);

JUnit XML not generated

Cause: The Maven Surefire Plugin is not configured or the test phase was skipped.

Fix: Ensure the Surefire Plugin is included in your pom.xml:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.2.5</version>
    <configuration>
        <statelessTestsetReporter
            implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
            <usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
        </statelessTestsetReporter>
    </configuration>
</plugin>

Verify that target/surefire-reports/ contains XML files after running mvn test. If the directory is empty, check that your test classes follow the Surefire naming convention (*Test.java, Test*.java, or *Tests.java).