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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

### Unreleased

- **`BaseCucumberTest` deprecated** — `@Deprecated(since = "3.9.0")`, because `AbstractTestNGCucumberTests` is deprecated for removal upstream in Cucumber 8.x. Nothing changes at runtime and existing TestNG Cucumber suites keep working; subclasses now get a deprecation warning. Plan to remove it in 4.0.0, once Cucumber drops its TestNG runner, in favour of `cucumber-junit-platform-engine` (added as an optional dependency). `CucumberStepLogger` also moves off the deprecated `getStepText()`. (#155, #157)

- **JUnit Platform: suite-start hooks now fire** — `ExecutionHook.onSuiteStart()` was never called on the JUnit 5 path (only TestNG), so hooks that open a run or start a clock silently did nothing. `SeleniumBootLauncherListener` now bootstraps the framework and fires `onSuiteStart()` once per test plan.

- **Failed tests with trace links in GitHub Actions job summary** — when running inside GitHub Actions and `tracing.enabled: true` is on, failed tests are automatically summarized in a Markdown table (test name, error message, and path to the HTML trace file) appended to `$GITHUB_STEP_SUMMARY`. Triage failures and locate trace paths directly from the Actions job summary without downloading artifact bundles; no-op outside Actions or when all tests pass. (#137)
Expand Down
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>8.0.3</version>
<optional>true</optional>
</dependency>

<!--
JUnit 5 support — optional so it is NOT pulled transitively for TestNG users.
Consumers who use JUnit 5 must add junit-jupiter and junit-platform-launcher
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/seleniumboot/cucumber/BaseCucumberTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,14 @@
*
* <p>Step definition classes should extend {@link BaseCucumberSteps} to get
* {@code getDriver()}, {@code open()}, {@code $()} and {@code assertThat()}.
*
* @deprecated since 3.9.0. {@link AbstractTestNGCucumberTests} has been deprecated for
* removal upstream in Cucumber 8.x. New suites should use JUnit Platform Engine
* ({@code cucumber-junit-platform-engine}) with {@code @Suite}. This class will be
* removed in 4.0.0 once Cucumber drops TestNG runner support.
*/
@Deprecated(since = "3.9.0")
@SuppressWarnings({"deprecation", "removal"})
@SeleniumBootApi(since = "1.9.0")
@Listeners({SuiteExecutionListener.class, TestExecutionListener.class})
public abstract class BaseCucumberTest extends AbstractTestNGCucumberTests {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ private void onStepStarted(TestStepStarted event) {
return;
}
if (event.getTestStep() instanceof PickleStepTestStep step) {
CURRENT_STEP.set(step.getStepText());
CURRENT_STEP.set(step.getStep().getText());
}
}

Expand Down
177 changes: 177 additions & 0 deletions src/test/java/com/seleniumboot/unit/CucumberStepLoggerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package com.seleniumboot.unit;

import com.seleniumboot.cucumber.CucumberStepLogger;
import com.seleniumboot.steps.StepLogger;
import com.seleniumboot.steps.StepStatus;
import io.cucumber.plugin.event.EventHandler;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.HookTestStep;
import io.cucumber.plugin.event.PickleStepTestStep;
import io.cucumber.plugin.event.Result;
import io.cucumber.plugin.event.Status;
import io.cucumber.plugin.event.Step;
import io.cucumber.plugin.event.TestStepFinished;
import io.cucumber.plugin.event.TestStepStarted;
import org.mockito.ArgumentCaptor;
import org.mockito.MockedStatic;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
import static org.testng.Assert.*;

/**
* Unit tests for {@link CucumberStepLogger}.
*/
public class CucumberStepLoggerTest {

private MockedStatic<StepLogger> stepLoggerMock;

@BeforeMethod
public void setup() {
stepLoggerMock = mockStatic(StepLogger.class);
}

@AfterMethod
public void teardown() {
stepLoggerMock.close();
}

@Test
public void registersHandlersAndLogsSuccessfulStep() {
CucumberStepLogger cucumberStepLogger = new CucumberStepLogger();
EventPublisher publisher = mock(EventPublisher.class);

ArgumentCaptor<EventHandler> handlerCaptor = ArgumentCaptor.forClass(EventHandler.class);
cucumberStepLogger.setEventPublisher(publisher);

verify(publisher).registerHandlerFor(eq(TestStepStarted.class), handlerCaptor.capture());
verify(publisher).registerHandlerFor(eq(TestStepFinished.class), handlerCaptor.capture());

EventHandler<TestStepStarted> startHandler = handlerCaptor.getAllValues().get(0);
EventHandler<TestStepFinished> finishHandler = handlerCaptor.getAllValues().get(1);

// Given a pickle step
PickleStepTestStep pickleStep = mock(PickleStepTestStep.class);
Step step = mock(Step.class);
when(step.getText()).thenReturn("Given user navigates to login");
when(pickleStep.getStep()).thenReturn(step);

TestStepStarted startEvent = mock(TestStepStarted.class);
when(startEvent.getTestStep()).thenReturn(pickleStep);

TestStepFinished finishEvent = mock(TestStepFinished.class);
when(finishEvent.getTestStep()).thenReturn(pickleStep);
Result result = mock(Result.class);
when(result.getStatus()).thenReturn(Status.PASSED);
when(finishEvent.getResult()).thenReturn(result);

// When step starts and finishes
startHandler.receive(startEvent);
finishHandler.receive(finishEvent);

// Then logged to StepLogger with PASS and no screenshot
stepLoggerMock.verify(() -> StepLogger.step("Given user navigates to login", StepStatus.PASS, false), times(1));
}

@Test
public void logsFailedStepWithScreenshot() {
CucumberStepLogger cucumberStepLogger = new CucumberStepLogger();
EventPublisher publisher = mock(EventPublisher.class);

ArgumentCaptor<EventHandler> handlerCaptor = ArgumentCaptor.forClass(EventHandler.class);
cucumberStepLogger.setEventPublisher(publisher);

verify(publisher).registerHandlerFor(eq(TestStepStarted.class), handlerCaptor.capture());
verify(publisher).registerHandlerFor(eq(TestStepFinished.class), handlerCaptor.capture());

EventHandler<TestStepStarted> startHandler = handlerCaptor.getAllValues().get(0);
EventHandler<TestStepFinished> finishHandler = handlerCaptor.getAllValues().get(1);

PickleStepTestStep pickleStep = mock(PickleStepTestStep.class);
Step step = mock(Step.class);
when(step.getText()).thenReturn("When user enters invalid password");
when(pickleStep.getStep()).thenReturn(step);

TestStepStarted startEvent = mock(TestStepStarted.class);
when(startEvent.getTestStep()).thenReturn(pickleStep);

TestStepFinished finishEvent = mock(TestStepFinished.class);
when(finishEvent.getTestStep()).thenReturn(pickleStep);
Result result = mock(Result.class);
when(result.getStatus()).thenReturn(Status.FAILED);
when(finishEvent.getResult()).thenReturn(result);

startHandler.receive(startEvent);
finishHandler.receive(finishEvent);

// Failure must trigger screenshot = true
stepLoggerMock.verify(() -> StepLogger.step("When user enters invalid password", StepStatus.FAIL, true), times(1));
}

@Test
public void mapsSkippedStatusToWarn() {
CucumberStepLogger cucumberStepLogger = new CucumberStepLogger();
EventPublisher publisher = mock(EventPublisher.class);

ArgumentCaptor<EventHandler> handlerCaptor = ArgumentCaptor.forClass(EventHandler.class);
cucumberStepLogger.setEventPublisher(publisher);

verify(publisher).registerHandlerFor(eq(TestStepStarted.class), handlerCaptor.capture());
verify(publisher).registerHandlerFor(eq(TestStepFinished.class), handlerCaptor.capture());

EventHandler<TestStepStarted> startHandler = handlerCaptor.getAllValues().get(0);
EventHandler<TestStepFinished> finishHandler = handlerCaptor.getAllValues().get(1);

PickleStepTestStep pickleStep = mock(PickleStepTestStep.class);
Step step = mock(Step.class);
when(step.getText()).thenReturn("Then dashboard should display");
when(pickleStep.getStep()).thenReturn(step);

TestStepStarted startEvent = mock(TestStepStarted.class);
when(startEvent.getTestStep()).thenReturn(pickleStep);

TestStepFinished finishEvent = mock(TestStepFinished.class);
when(finishEvent.getTestStep()).thenReturn(pickleStep);
Result result = mock(Result.class);
when(result.getStatus()).thenReturn(Status.SKIPPED);
when(finishEvent.getResult()).thenReturn(result);

startHandler.receive(startEvent);
finishHandler.receive(finishEvent);

stepLoggerMock.verify(() -> StepLogger.step("Then dashboard should display", StepStatus.WARN, false), times(1));
}

@Test
public void ignoresHookTestSteps() {
CucumberStepLogger cucumberStepLogger = new CucumberStepLogger();
EventPublisher publisher = mock(EventPublisher.class);

ArgumentCaptor<EventHandler> handlerCaptor = ArgumentCaptor.forClass(EventHandler.class);
cucumberStepLogger.setEventPublisher(publisher);

verify(publisher).registerHandlerFor(eq(TestStepStarted.class), handlerCaptor.capture());
verify(publisher).registerHandlerFor(eq(TestStepFinished.class), handlerCaptor.capture());

EventHandler<TestStepStarted> startHandler = handlerCaptor.getAllValues().get(0);
EventHandler<TestStepFinished> finishHandler = handlerCaptor.getAllValues().get(1);

HookTestStep hookStep = mock(HookTestStep.class);
TestStepStarted startEvent = mock(TestStepStarted.class);
when(startEvent.getTestStep()).thenReturn(hookStep);

TestStepFinished finishEvent = mock(TestStepFinished.class);
when(finishEvent.getTestStep()).thenReturn(hookStep);

startHandler.receive(startEvent);
finishHandler.receive(finishEvent);

// Hook step must never be logged to StepLogger
stepLoggerMock.verify(() -> StepLogger.step(any(), any(), anyBoolean()), never());
}
}

Loading