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 junitwhen running Selenium tests with JUnit (Java) or pytest (Python)--format testngwhen 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 pytestProject Setup
Java — Maven Project Structure
my-selenium-project/
├── pom.xml
└── src/
└── test/
└── java/
└── com/
└── example/
├── LoginTest.java
└── SearchTest.javaYour 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.pyrequirements.txt:
selenium>=4.18.0
pytest>=8.0.0Writing 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 TestKase48272→ 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 testJUnit 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.xmlThis 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.xmlUse --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:
| Runner | Where to Embed the ID | Example |
|---|---|---|
| Java (JUnit 5) | @DisplayName annotation — requires Surefire's usePhrasedTestCaseMethodName (Option A above) | @DisplayName("[48271] testValidLogin") |
| Java (JUnit 5), no Surefire config | Test 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 ID | pytest.param(..., id="[48271]") |
| JavaScript (Mocha/Jest) | it() or test() name | test('[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 test4. 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 --verbose5. 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 nameCause: 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.xmlFix: 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 referenceCause: 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).
