diff --git a/CHANGELOG.md b/CHANGELOG.md index 046324a..40de4bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ### Unreleased +- **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) - **Bounded failure detail record** — every failed test now exports one optional-field `failureDetail` object with the failing locator, top application frame, last successful step, page URL/title, heal events, console errors, retry/history status, and representative same-cause context. The HTML failure panel and opt-in AI prompt reuse this record; raw errors and stack traces remain available. Text/list/URL limits and the exact AI data disclosure are documented. (#142) diff --git a/docs-site/docs/changelog.md b/docs-site/docs/changelog.md index 9ef5138..0f9bdec 100644 --- a/docs-site/docs/changelog.md +++ b/docs-site/docs/changelog.md @@ -11,6 +11,8 @@ All notable changes to Selenium Boot are documented here. ## [Unreleased] +- **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) - **Bounded failure detail record** — every failed test now exports one optional-field `failureDetail` object with the failing locator, top application frame, last successful step, page URL/title, heal events, console errors, retry/history status, and representative same-cause context. The HTML failure panel and opt-in AI prompt reuse this record; raw errors and stack traces remain available. Text/list/URL limits and the exact AI data disclosure are documented. (#142) diff --git a/src/main/java/com/seleniumboot/junit5/SeleniumBootLauncherListener.java b/src/main/java/com/seleniumboot/junit5/SeleniumBootLauncherListener.java index ad129fd..4745bcf 100644 --- a/src/main/java/com/seleniumboot/junit5/SeleniumBootLauncherListener.java +++ b/src/main/java/com/seleniumboot/junit5/SeleniumBootLauncherListener.java @@ -7,6 +7,7 @@ import com.seleniumboot.flakiness.FlakinessAnalyzer; import com.seleniumboot.hooks.HookRegistry; import com.seleniumboot.internal.SeleniumBootContext; +import com.seleniumboot.lifecycle.FrameworkBootstrap; import com.seleniumboot.metrics.ExecutionMetrics; import com.seleniumboot.reporting.JUnitXmlReporter; import com.seleniumboot.precondition.PreConditionRunner; @@ -29,6 +30,22 @@ @SeleniumBootApi(since = "1.9.0") public class SeleniumBootLauncherListener implements TestExecutionListener { + /** + * Fires suite-start hooks once per test plan — the JUnit 5 equivalent of + * {@code SuiteExecutionListener.onStart()}. Bootstrap is idempotent, so this is + * safe alongside {@link SeleniumBootExtension#beforeAll}. A bootstrap failure is + * left for the extension to surface with its full cause chain. + */ + @Override + public void testPlanExecutionStarted(TestPlan testPlan) { + try { + FrameworkBootstrap.initialize(); + HookRegistry.onSuiteStart(); + } catch (Exception e) { + System.err.println("[Selenium Boot] Suite start hooks skipped: " + e.getMessage()); + } + } + @Override public void testPlanExecutionFinished(TestPlan testPlan) { try { diff --git a/src/test/java/com/seleniumboot/unit/SeleniumBootLauncherListenerTest.java b/src/test/java/com/seleniumboot/unit/SeleniumBootLauncherListenerTest.java new file mode 100644 index 0000000..f3e4cbf --- /dev/null +++ b/src/test/java/com/seleniumboot/unit/SeleniumBootLauncherListenerTest.java @@ -0,0 +1,45 @@ +package com.seleniumboot.unit; + +import com.seleniumboot.hooks.ExecutionHook; +import com.seleniumboot.hooks.HookRegistry; +import com.seleniumboot.junit5.SeleniumBootLauncherListener; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.Test; + +import java.lang.reflect.Field; +import java.util.List; + +import static org.testng.Assert.assertEquals; + +/** + * Unit tests for {@link SeleniumBootLauncherListener} — the JUnit Platform + * counterpart of {@code SuiteExecutionListener}. + */ +public class SeleniumBootLauncherListenerTest { + + @AfterMethod + public void resetRegistry() throws Exception { + Field f = HookRegistry.class.getDeclaredField("hooks"); + f.setAccessible(true); + ((List) f.get(null)).clear(); + } + + @Test + public void testPlanExecutionStarted_firesSuiteStartHookOnce() { + CountingHook hook = new CountingHook(); + HookRegistry.register(hook); + + new SeleniumBootLauncherListener().testPlanExecutionStarted(null); + + assertEquals(hook.suiteStarts, 1, "onSuiteStart must fire once per test plan"); + } + + private static final class CountingHook implements ExecutionHook { + int suiteStarts; + + @Override + public void onSuiteStart() { + suiteStarts++; + } + } +}