From 645e7014eb957dfeb8e64f4beb6e7a0b210bde7f Mon Sep 17 00:00:00 2001 From: Mohammad Saddam Hossain Date: Tue, 14 Jul 2026 13:15:09 +0530 Subject: [PATCH] test(steps): add unit coverage for StepLogger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #8 The steps/ package had no unit tests. Adds StepLoggerTest covering the previously-untested branches: - no active test context -> step is ignored (nothing recorded, no capture) for both step() and stepWithScreenshot() - INFO status recorded without a screenshot - screenshot=true -> ScreenshotManager.captureAsBase64() captured & attached - explicit StepStatus preserved - offsetMs = 0 when the test start time is unknown - stepWithScreenshot stores the pre-captured base64 as-is (no live capture) Collaborators (SeleniumBootContext, ExecutionMetrics, ScreenshotManager) are all static, mocked with mockStatic — no real browser required. mvn clean verify: 480 passing. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../com/seleniumboot/unit/StepLoggerTest.java | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 src/test/java/com/seleniumboot/unit/StepLoggerTest.java diff --git a/src/test/java/com/seleniumboot/unit/StepLoggerTest.java b/src/test/java/com/seleniumboot/unit/StepLoggerTest.java new file mode 100644 index 0000000..91d0e3c --- /dev/null +++ b/src/test/java/com/seleniumboot/unit/StepLoggerTest.java @@ -0,0 +1,153 @@ +package com.seleniumboot.unit; + +import com.seleniumboot.internal.SeleniumBootContext; +import com.seleniumboot.metrics.ExecutionMetrics; +import com.seleniumboot.reporting.ScreenshotManager; +import com.seleniumboot.steps.StepLogger; +import com.seleniumboot.steps.StepRecord; +import com.seleniumboot.steps.StepStatus; +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 StepLogger}. + * + *

All collaborators ({@link SeleniumBootContext}, {@link ExecutionMetrics}, + * {@link ScreenshotManager}) are static, so they are mocked with + * {@code mockStatic} — no real browser or test run required. + */ +public class StepLoggerTest { + + private static final String TEST_ID = "com.example.LoginTest.login"; + + private MockedStatic contextMock; + private MockedStatic metricsMock; + private MockedStatic screenshotMock; + + @BeforeMethod + public void setup() { + contextMock = mockStatic(SeleniumBootContext.class); + metricsMock = mockStatic(ExecutionMetrics.class); + screenshotMock = mockStatic(ScreenshotManager.class); + } + + @AfterMethod + public void teardown() { + contextMock.close(); + metricsMock.close(); + screenshotMock.close(); + } + + // ── No active test context → step is ignored ─────────────────────────────── + + @Test + public void step_isIgnored_whenNoActiveTestContext() { + contextMock.when(SeleniumBootContext::getCurrentTestId).thenReturn(null); + + StepLogger.step("orphan step"); + + // Nothing recorded, no screenshot attempted + metricsMock.verify(() -> ExecutionMetrics.recordStep(any(), any()), never()); + screenshotMock.verify(ScreenshotManager::captureAsBase64, never()); + } + + // ── Basic step: INFO status, no screenshot ───────────────────────────────── + + @Test + public void step_recordsInfoStatus_withoutScreenshot() { + contextMock.when(SeleniumBootContext::getCurrentTestId).thenReturn(TEST_ID); + metricsMock.when(() -> ExecutionMetrics.getTestStartTime(TEST_ID)) + .thenReturn(System.currentTimeMillis() - 1000); + + StepLogger.step("Navigate to login page"); + + StepRecord record = captureRecordedStep(); + assertEquals(record.getName(), "Navigate to login page"); + assertEquals(record.getStatus(), StepStatus.INFO.name()); + assertNull(record.getScreenshotBase64(), "no screenshot expected"); + assertTrue(record.getOffsetMs() >= 0, "offset should be computed from start time"); + screenshotMock.verify(ScreenshotManager::captureAsBase64, never()); + } + + // ── Screenshot requested → captured and attached ─────────────────────────── + + @Test + public void step_capturesScreenshot_whenRequested() { + contextMock.when(SeleniumBootContext::getCurrentTestId).thenReturn(TEST_ID); + metricsMock.when(() -> ExecutionMetrics.getTestStartTime(TEST_ID)).thenReturn(0L); + screenshotMock.when(ScreenshotManager::captureAsBase64).thenReturn("BASE64DATA"); + + StepLogger.step("After credentials entered", true); + + StepRecord record = captureRecordedStep(); + assertEquals(record.getScreenshotBase64(), "BASE64DATA"); + screenshotMock.verify(ScreenshotManager::captureAsBase64, times(1)); + } + + // ── Explicit status is preserved ─────────────────────────────────────────── + + @Test + public void step_recordsExplicitStatus() { + contextMock.when(SeleniumBootContext::getCurrentTestId).thenReturn(TEST_ID); + metricsMock.when(() -> ExecutionMetrics.getTestStartTime(TEST_ID)) + .thenReturn(System.currentTimeMillis()); + + StepLogger.step("Verify dashboard visible", StepStatus.PASS); + + assertEquals(captureRecordedStep().getStatus(), StepStatus.PASS.name()); + } + + // ── offsetMs is 0 when the start time is unknown ─────────────────────────── + + @Test + public void step_offsetIsZero_whenStartTimeUnknown() { + contextMock.when(SeleniumBootContext::getCurrentTestId).thenReturn(TEST_ID); + metricsMock.when(() -> ExecutionMetrics.getTestStartTime(TEST_ID)).thenReturn(0L); + + StepLogger.step("no start time"); + + assertEquals(captureRecordedStep().getOffsetMs(), 0L); + } + + // ── stepWithScreenshot: pre-captured base64 is stored as-is ──────────────── + + @Test + public void stepWithScreenshot_storesProvidedBase64() { + contextMock.when(SeleniumBootContext::getCurrentTestId).thenReturn(TEST_ID); + metricsMock.when(() -> ExecutionMetrics.getTestStartTime(TEST_ID)) + .thenReturn(System.currentTimeMillis()); + + StepLogger.stepWithScreenshot("Visual diff", StepStatus.FAIL, "DIFFIMG"); + + StepRecord record = captureRecordedStep(); + assertEquals(record.getScreenshotBase64(), "DIFFIMG"); + assertEquals(record.getStatus(), StepStatus.FAIL.name()); + // stepWithScreenshot never triggers a live capture + screenshotMock.verify(ScreenshotManager::captureAsBase64, never()); + } + + @Test + public void stepWithScreenshot_isIgnored_whenNoActiveTestContext() { + contextMock.when(SeleniumBootContext::getCurrentTestId).thenReturn(null); + + StepLogger.stepWithScreenshot("orphan", StepStatus.INFO, "IMG"); + + metricsMock.verify(() -> ExecutionMetrics.recordStep(any(), any()), never()); + } + + // ── helper ───────────────────────────────────────────────────────────────── + + private StepRecord captureRecordedStep() { + ArgumentCaptor captor = ArgumentCaptor.forClass(StepRecord.class); + metricsMock.verify(() -> ExecutionMetrics.recordStep(eq(TEST_ID), captor.capture())); + return captor.getValue(); + } +}