Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 75 additions & 5 deletions docs-site/docs/cucumber.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,60 @@ Selenium Boot integrates with Cucumber 7 out of the box. Driver lifecycle, the H

## Setup

Add Cucumber to your project alongside Selenium Boot:
Add Cucumber to your project alongside Selenium Boot.

### Option A: JUnit Platform Engine (Recommended)

```xml title="pom.xml"
<dependency>
<groupId>io.github.seleniumboot</groupId>
<artifactId>selenium-boot</artifactId>
<version>3.8.0</version>
</dependency>

<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>8.0.3</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>8.0.3</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>6.1.3</version>
<scope>test</scope>
</dependency>
```

### Option B: TestNG (Legacy / Backwards Compatible)

```xml title="pom.xml"
<dependency>
<groupId>io.github.seleniumboot</groupId>
<artifactId>selenium-boot</artifactId>
<version>1.11.0</version>
<version>3.8.0</version>
</dependency>

<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.20.1</version>
<version>8.0.3</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-testng</artifactId>
<version>7.20.1</version>
<version>8.0.3</version>
<scope>test</scope>
</dependency>
```

Expand Down Expand Up @@ -59,7 +94,38 @@ src/

## Runner class

Annotate with `@CucumberOptions` and extend `BaseCucumberTest`. No other code needed:
### Option A: JUnit Platform Suite (`@Suite` — Recommended)

Use JUnit Platform's standard `@Suite` runner with `cucumber-junit-platform-engine`. No base class is needed:

```java
package com.yourcompany.bdd;

import org.junit.platform.suite.api.ConfigurationParameter;
import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;

import static io.cucumber.junit.platform.engine.Constants.GLUE_PROPERTY_NAME;
import static io.cucumber.junit.platform.engine.Constants.PLUGIN_PROPERTY_NAME;

@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.yourcompany.bdd.steps, com.seleniumboot.cucumber")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty, com.seleniumboot.cucumber.CucumberStepLogger")
public class CucumberSuiteTest {}
```

Alternatively, configure glue and plugins in `src/test/resources/junit-platform.properties`:
```properties title="junit-platform.properties"
cucumber.glue=com.yourcompany.bdd.steps, com.seleniumboot.cucumber
cucumber.plugin=pretty, com.seleniumboot.cucumber.CucumberStepLogger
```

### Option B: TestNG Runner (`BaseCucumberTest` — Deprecated in 3.9.0)

Annotate with `@CucumberOptions` and extend `BaseCucumberTest`:

```java
@CucumberOptions(
Expand All @@ -70,6 +136,10 @@ Annotate with `@CucumberOptions` and extend `BaseCucumberTest`. No other code ne
public class CucumberRunner extends BaseCucumberTest {}
```

:::note Deprecation Notice
`BaseCucumberTest` is deprecated as of Selenium Boot 3.9.0 because upstream `AbstractTestNGCucumberTests` has been deprecated for removal in Cucumber 8.x. `BaseCucumberTest` continues to function for all TestNG suites throughout the 3.x series and will be removed in 4.0.0.
:::

`"com.seleniumboot.cucumber"` in `glue` is required — it tells Cucumber where to find `CucumberHooks`, which manages the driver lifecycle.

`CucumberStepLogger` in `plugin` streams Gherkin step names into the Selenium Boot HTML report step timeline.
Expand Down
24 changes: 20 additions & 4 deletions src/main/java/com/seleniumboot/cucumber/CucumberHooks.java
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ private static void applyRetryTag(Scenario scenario) {
CucumberRetryContext.clear();
}

private static void checkCucumberQuarantine(Scenario scenario) {
static void checkCucumberQuarantine(Scenario scenario) {
try {
com.seleniumboot.config.SeleniumBootConfig.Quarantine cfg =
SeleniumBootContext.getConfig().getQuarantine();
Expand All @@ -228,7 +228,7 @@ private static void checkCucumberQuarantine(Scenario scenario) {
for (String t : scenario.getSourceTagNames()) {
String normalized = t.startsWith("@") ? t.substring(1) : t;
if (normalized.equalsIgnoreCase(configuredTag)) {
throw new org.testng.SkipException(
abortQuarantinedScenario(
"[Quarantined] " + scenario.getName() + " — @" + configuredTag + " tag present"
);
}
Expand All @@ -240,16 +240,32 @@ private static void checkCucumberQuarantine(Scenario scenario) {
java.util.Collection<String> tags = scenario.getSourceTagNames();

if (QuarantineLoader.isQuarantinedScenario(tags, featureUri, scenarioName)) {
throw new org.testng.SkipException(
abortQuarantinedScenario(
"[Quarantined] " + scenarioName + " — "
+ QuarantineLoader.getScenarioReason(tags, featureUri, scenarioName)
);
}
} catch (org.testng.SkipException e) {
} catch (org.testng.SkipException | org.opentest4j.TestAbortedException e) {
throw e;
} catch (Exception ignored) {}
}

private static void abortQuarantinedScenario(String reason) {
if (isTestNgExecution()) {
throw new org.testng.SkipException(reason);
} else {
throw new org.opentest4j.TestAbortedException(reason);
}
}

private static boolean isTestNgExecution() {
try {
return org.testng.Reporter.getCurrentTestResult() != null;
} catch (Throwable ignored) {
return false;
}
}

private void safeQuitDriver() {
try {
if (DriverManager.shouldQuitAfterTest()) DriverManager.quitDriver();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,17 @@
import com.seleniumboot.internal.SeleniumBootContext;
import com.seleniumboot.lifecycle.FrameworkBootstrap;
import com.seleniumboot.metrics.ExecutionMetrics;
import com.seleniumboot.precondition.ApiHealthChecker;
import com.seleniumboot.precondition.PreConditionRegistry;
import com.seleniumboot.reporting.JUnitXmlReporter;
import com.seleniumboot.precondition.PreConditionRunner;
import com.seleniumboot.reporting.ReportAdapterRegistry;
import com.seleniumboot.testmanagement.TestManagementReporter;
import org.junit.platform.launcher.TestExecutionListener;
import org.junit.platform.launcher.TestPlan;

import java.util.concurrent.atomic.AtomicBoolean;

/**
* JUnit Platform launcher listener that generates Selenium Boot reports after
* the entire test plan finishes — the JUnit 5 equivalent of
Expand All @@ -30,6 +34,8 @@
@SeleniumBootApi(since = "1.9.0")
public class SeleniumBootLauncherListener implements TestExecutionListener {

private static final AtomicBoolean suiteStarted = new AtomicBoolean(false);

/**
* Fires suite-start hooks once per test plan — the JUnit 5 equivalent of
* {@code SuiteExecutionListener.onStart()}. Bootstrap is idempotent, so this is
Expand All @@ -38,9 +44,13 @@ public class SeleniumBootLauncherListener implements TestExecutionListener {
*/
@Override
public void testPlanExecutionStarted(TestPlan testPlan) {
if (!suiteStarted.compareAndSet(false, true)) return;
try {
FrameworkBootstrap.initialize();
ApiHealthChecker.clearCache();
PreConditionRegistry.loadAll();
HookRegistry.onSuiteStart();
TestManagementReporter.getInstance().onSuiteStart();
} catch (Exception e) {
System.err.println("[Selenium Boot] Suite start hooks skipped: " + e.getMessage());
}
Expand Down Expand Up @@ -70,6 +80,8 @@ public void testPlanExecutionFinished(TestPlan testPlan) {
throw e; // re-throw CI gate failures (pass-rate / flakiness)
} catch (Exception e) {
System.err.println("[Selenium Boot] Report generation failed: " + e.getMessage());
} finally {
suiteStarted.set(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public final class TestManagementReporter {
private int activeTestRailRunId = 0;
private boolean testRailEnabled = false;
private boolean xrayEnabled = false;
private final java.util.concurrent.atomic.AtomicBoolean suiteStarted = new java.util.concurrent.atomic.AtomicBoolean(false);

private TestManagementReporter() {}

Expand All @@ -67,6 +68,7 @@ public static TestManagementReporter getInstance() {
* and optionally create a TestRail run.
*/
public void onSuiteStart() {
if (!suiteStarted.compareAndSet(false, true)) return;
SeleniumBootConfig.TestManagement cfg = loadConfig();
if (cfg == null) return;

Expand Down Expand Up @@ -145,6 +147,7 @@ public void onSuiteEnd() {
activeTestRailRunId = 0;
testRailClient = null;
xrayClient = null;
suiteStarted.set(false);
}

// ── internals ─────────────────────────────────────────────────────────────
Expand Down
Loading
Loading