From ea881e7350c958961d50bc0d3b976816a9c2dabc Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 9 Jun 2022 10:32:42 +0300 Subject: [PATCH 1/6] Broader catch in screenshot and page source getting methods --- CHANGELOG.md | 2 ++ .../selenide/ReportPortalSelenideEventListener.java | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7469f0..a63b663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +### Changed +- Broader catch in screenshot and page source getting methods to avoid throwing any exceptions in logger, by @HardNorth ## [5.1.1] ### Changed diff --git a/src/main/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListener.java b/src/main/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListener.java index f7bce03..2e4e0a6 100644 --- a/src/main/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListener.java +++ b/src/main/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListener.java @@ -29,7 +29,6 @@ import com.google.common.io.ByteSource; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; -import org.openqa.selenium.WebDriverException; import javax.annotation.Nonnull; import java.nio.charset.StandardCharsets; @@ -130,7 +129,7 @@ private void logScreenshot() { if ((screenshot = ((TakesScreenshot) WebDriverRunner.getWebDriver()).getScreenshotAs(OutputType.BYTES)) != null) { attachBinary(SCREENSHOT_MESSAGE, screenshot, SELENIUM_SCREENSHOT_TYPE); } - } catch (WebDriverException e) { + } catch (Exception e) { ReportPortal.emitLog("Unable to get WebDriver screenshot: " + e.getMessage(), LogLevel.ERROR.name(), Calendar.getInstance().getTime() @@ -146,7 +145,7 @@ private void logPageSource() { if ((pageSource = WebDriverRunner.getWebDriver().getPageSource()) != null) { attachBinary(PAGE_SOURCE_MESSAGE, pageSource.getBytes(StandardCharsets.UTF_8), SELENIUM_PAGE_SOURCE_TYPE); } - } catch (WebDriverException e) { + } catch (Exception e) { ReportPortal.emitLog("Unable to get WebDriver page source: " + e.getMessage(), LogLevel.ERROR.name(), Calendar.getInstance().getTime() From f2834eedd4c5df008e621da258a88b107b465602 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 9 Jun 2022 10:43:53 +0300 Subject: [PATCH 2/6] Constants refactoring --- .../selenide/ReportPortalSelenideEventListener.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListener.java b/src/main/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListener.java index 2e4e0a6..9140068 100644 --- a/src/main/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListener.java +++ b/src/main/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListener.java @@ -40,8 +40,7 @@ public class ReportPortalSelenideEventListener implements LogEventListener { - public static final String UNABLE_TO_PROCESS_SELENIDE_EVENT_STATUS = "Unable to process selenide event status, skipping it: "; - public static final Function DEFAULT_LOG_CONVERTER = log -> log; + public static final Function DEFAULT_STEP_NAME_CONVERTER = log -> log; private static final String SCREENSHOT_MESSAGE = "Screenshot"; private static final String PAGE_SOURCE_MESSAGE = "Page source"; @@ -65,7 +64,7 @@ public ReportPortalSelenideEventListener(@Nonnull LogLevel defaultLogLevel, Func } public ReportPortalSelenideEventListener(@Nonnull LogLevel defaultLogLevel) { - this(defaultLogLevel, DEFAULT_LOG_CONVERTER); + this(defaultLogLevel, DEFAULT_STEP_NAME_CONVERTER); } public ReportPortalSelenideEventListener() { @@ -178,7 +177,7 @@ public void afterEvent(@Nonnull LogEvent currentLog) { } else if (LogEvent.EventStatus.PASS.equals(currentLog.getStatus())) { ofNullable(Launch.currentLaunch()).ifPresent(l -> l.getStepReporter().finishPreviousStep()); } else { - ReportPortal.emitLog(UNABLE_TO_PROCESS_SELENIDE_EVENT_STATUS + currentLog.getStatus(), + ReportPortal.emitLog("Unable to process selenide event status, skipping it: " + currentLog.getStatus(), LogLevel.WARN.name(), Calendar.getInstance().getTime() ); From 352a0ff85eaf8e8da25b82ca38336e7c156fed03 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 9 Jun 2022 10:44:02 +0300 Subject: [PATCH 3/6] Tests --- ...ReportPortalSelenideEventListenerTest.java | 75 +++++++++++++++++-- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/src/test/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListenerTest.java b/src/test/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListenerTest.java index 930b034..2d918e1 100644 --- a/src/test/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListenerTest.java +++ b/src/test/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListenerTest.java @@ -120,18 +120,17 @@ public void test_step_logging_failed() { when(webDriver.getScreenshotAs(eq(OutputType.BYTES))).thenReturn(image); when(webDriver.getPageSource()).thenReturn(page); + ReportPortalSelenideEventListener listener = new ReportPortalSelenideEventListener(); + runEvent(listener::beforeEvent, logEvent); + verify(context, noInteractions()).emit(any(Function.class)); + verify(stepReporter).sendStep(eq(ItemStatus.INFO), eq(SELENIDE_LOG_STRING)); + + when(logEvent.getStatus()).thenReturn(LogEvent.EventStatus.FAIL); + try (MockedStatic driverMockedStatic = Mockito.mockStatic(WebDriverRunner.class)) { driverMockedStatic.when(WebDriverRunner::hasWebDriverStarted).thenReturn(true); driverMockedStatic.when(WebDriverRunner::getWebDriver).thenReturn(webDriver); - ReportPortalSelenideEventListener listener = new ReportPortalSelenideEventListener(); - runEvent(listener::beforeEvent, logEvent); - verify(context, noInteractions()).emit(any(Function.class)); - - verify(stepReporter).sendStep(eq(ItemStatus.INFO), eq(SELENIDE_LOG_STRING)); - - when(logEvent.getStatus()).thenReturn(LogEvent.EventStatus.FAIL); - List> logs = runEventCapture(listener::afterEvent, logEvent); verify(stepReporter).finishPreviousStep(eq(ItemStatus.FAILED)); assertThat(logs, hasSize(2)); @@ -227,4 +226,64 @@ public void test_step_logging_failed_browser_logs() { } } } + + @Test + @SuppressWarnings({ "ResultOfMethodCallIgnored", "unchecked" }) + public void test_step_logging_failed_screenshot_exception() { + String exceptionMessage = "my exception message"; + LogEvent logEvent = mock(SelenideLog.class); + when(logEvent.toString()).thenReturn(SELENIDE_LOG_STRING); + ReportPortalSelenideEventListener listener = new ReportPortalSelenideEventListener().logPageSources(false); + runEvent(listener::beforeEvent, logEvent); + verify(context, noInteractions()).emit(any(Function.class)); + verify(stepReporter).sendStep(eq(ItemStatus.INFO), eq(SELENIDE_LOG_STRING)); + + when(logEvent.getStatus()).thenReturn(LogEvent.EventStatus.FAIL); + + RemoteWebDriver webDriver = mock(RemoteWebDriver.class); + when(webDriver.getScreenshotAs(any(OutputType.class))).thenThrow(new RuntimeException(exceptionMessage)); + try (MockedStatic driverMockedStatic = Mockito.mockStatic(WebDriverRunner.class)) { + driverMockedStatic.when(WebDriverRunner::hasWebDriverStarted).thenReturn(true); + driverMockedStatic.when(WebDriverRunner::getWebDriver).thenReturn(webDriver); + + List> logs = runEventCapture(listener::afterEvent, logEvent); + verify(stepReporter).finishPreviousStep(eq(ItemStatus.FAILED)); + assertThat(logs, hasSize(1)); + + SaveLogRQ screenshotLog = logs.get(0).apply("test"); + assertThat(screenshotLog.getLevel(), equalTo(LogLevel.ERROR.name())); + assertThat(screenshotLog.getFile(), nullValue()); + assertThat(screenshotLog.getMessage(), equalTo("Unable to get WebDriver screenshot: " + exceptionMessage)); + } + } + + @Test + @SuppressWarnings({ "ResultOfMethodCallIgnored", "unchecked" }) + public void test_step_logging_failed_page_source_exception() { + String exceptionMessage = "my exception message"; + LogEvent logEvent = mock(SelenideLog.class); + when(logEvent.toString()).thenReturn(SELENIDE_LOG_STRING); + ReportPortalSelenideEventListener listener = new ReportPortalSelenideEventListener().logScreenshots(false); + runEvent(listener::beforeEvent, logEvent); + verify(context, noInteractions()).emit(any(Function.class)); + verify(stepReporter).sendStep(eq(ItemStatus.INFO), eq(SELENIDE_LOG_STRING)); + + when(logEvent.getStatus()).thenReturn(LogEvent.EventStatus.FAIL); + + RemoteWebDriver webDriver = mock(RemoteWebDriver.class); + when(webDriver.getPageSource()).thenThrow(new RuntimeException(exceptionMessage)); + try (MockedStatic driverMockedStatic = Mockito.mockStatic(WebDriverRunner.class)) { + driverMockedStatic.when(WebDriverRunner::hasWebDriverStarted).thenReturn(true); + driverMockedStatic.when(WebDriverRunner::getWebDriver).thenReturn(webDriver); + + List> logs = runEventCapture(listener::afterEvent, logEvent); + verify(stepReporter).finishPreviousStep(eq(ItemStatus.FAILED)); + assertThat(logs, hasSize(1)); + + SaveLogRQ pageSourceLog = logs.get(0).apply("test"); + assertThat(pageSourceLog.getLevel(), equalTo(LogLevel.ERROR.name())); + assertThat(pageSourceLog.getFile(), nullValue()); + assertThat(pageSourceLog.getMessage(), equalTo("Unable to get WebDriver page source: " + exceptionMessage)); + } + } } From 629c750200b47e34bc2bb2d8ba08a07437531420 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 9 Jun 2022 10:45:00 +0300 Subject: [PATCH 4/6] CHANGELOG.md update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a63b663..6816e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## [Unreleased] ### Changed - Broader catch in screenshot and page source getting methods to avoid throwing any exceptions in logger, by @HardNorth +- Constant names refactoring, by @HardNorth ## [5.1.1] ### Changed From ef95066c2f6b6efdede433690ddcce4fd099ede7 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 9 Jun 2022 11:25:03 +0300 Subject: [PATCH 5/6] Javadocs --- .../ReportPortalSelenideEventListener.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/main/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListener.java b/src/main/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListener.java index 9140068..f1ef701 100644 --- a/src/main/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListener.java +++ b/src/main/java/com/epam/reportportal/selenide/ReportPortalSelenideEventListener.java @@ -38,6 +38,17 @@ import static java.util.Optional.ofNullable; +/** + * Selenide step logging listener for Report Portal. + *

+ * The listener listen for Selenide log events and send them to Report Portal as steps. It has ability to log screenshots and page sources + * on failure, this is enabled by default. Also, it is possible to attach different types of WebDriver logs on failure. + *

+ * Basic usage: + *

+ *     SelenideLogger.addListener("Report Portal logger", new ReportPortalSelenideEventListener());
+ * 
+ */ public class ReportPortalSelenideEventListener implements LogEventListener { public static final Function DEFAULT_STEP_NAME_CONVERTER = log -> log; @@ -58,44 +69,95 @@ public class ReportPortalSelenideEventListener implements LogEventListener { private boolean screenshots = true; private boolean pageSources = true; + /** + * Create listener instance with specified log level and step name converter. + * + * @param defaultLogLevel logging level of attachments + * @param stepConverter step name converter, suitable to sanitize step string from secret data + */ public ReportPortalSelenideEventListener(@Nonnull LogLevel defaultLogLevel, Function stepConverter) { logLevel = defaultLogLevel.name(); converter = stepConverter; } + /** + * Create listener instance with specified log level. + * + * @param defaultLogLevel logging level of attachments + */ public ReportPortalSelenideEventListener(@Nonnull LogLevel defaultLogLevel) { this(defaultLogLevel, DEFAULT_STEP_NAME_CONVERTER); } + /** + * Create listener instance with default attachment {@link LogLevel}: "INFO". + */ public ReportPortalSelenideEventListener() { this(LogLevel.INFO); } + /** + * Set screenshot on failure logging enable/disable. Enabled by default. + * + * @param logScreenshots use false to disable screenshot logging + * @return self instance for convenience + */ public ReportPortalSelenideEventListener logScreenshots(boolean logScreenshots) { this.screenshots = logScreenshots; return this; } + /** + * Set page sources on failure logging enable/disable. Enabled by default. + * + * @param logPageSources use false to disable page sources logging + * @return self instance for convenience + */ public ReportPortalSelenideEventListener logPageSources(boolean logPageSources) { this.pageSources = logPageSources; return this; } + /** + * Enable certain selenium log attach on failure. + * + * @param logType a string from {@link org.openqa.selenium.logging.LogType} describing desired log type to be logged + * @param logLevel desired log level to see in attachment + * @return self instance for convenience + */ public ReportPortalSelenideEventListener enableSeleniumLogs(@Nonnull String logType, @Nonnull Level logLevel) { seleniumLogTypes.put(logType, logLevel); return this; } + /** + * Disable certain selenium log attach on failure. + * + * @param logType a string from {@link org.openqa.selenium.logging.LogType} describing desired log type to be muted + * @return self instance for convenience + */ public ReportPortalSelenideEventListener disableSeleniumLogs(@Nonnull String logType) { seleniumLogTypes.remove(logType); return this; } + /** + * Enable custom selenide step logging. + * + * @param selenideLogType type of selenide event to enable logging + * @return self instance for convenience + */ public ReportPortalSelenideEventListener enableSelenideLogs(@Nonnull Class selenideLogType) { selenideLogTypes.add(selenideLogType); return this; } + /** + * Disable custom selenide step logging. + * + * @param selenideLogType type of selenide event to mute + * @return self instance for convenience + */ public ReportPortalSelenideEventListener disableSelenideLogs(@Nonnull Class selenideLogType) { selenideLogTypes.remove(selenideLogType); return this; From 92dd9bbd699b7668143f888441177a34b15d9943 Mon Sep 17 00:00:00 2001 From: Vadzim Hushchanskou Date: Thu, 9 Jun 2022 11:48:48 +0300 Subject: [PATCH 6/6] READMEs update --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++----- README_TEMPLATE.md | 49 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 87 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7f58ce3..d49c896 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ The latest version: 5.1.1. Please use `Maven Central` link above to get the agen ## Overview -Selenide step logger for Report Portal - -Description TBD +Selenide step logging listener for Report Portal. The listener listen for Selenide log events and send them to Report Portal as steps. +It has ability to log screenshots and page sources on failure, this is enabled by default. Also, it is possible to attach different types of +WebDriver logs on failure. ## Configuration @@ -61,11 +61,49 @@ base class for all tests: ```java public class BaseTest { static { - SelenideLogger.addListener("ReportPortal logger", new ReportPortalSelenideEventListener()); + SelenideLogger.addListener("Report Portal logger", new ReportPortalSelenideEventListener()); } } ``` ### Logger configuration -TBD +#### Screenshots and page sources + +The logger has screenshots and page sources logging enabled by default. It logs them inside the step on every failure. To disable / enable +this behavior we have separate setters methods in the logger. +E.G.: +```java +public class BaseTest { + static { + SelenideLogger.addListener("Report Portal logger", + new ReportPortalSelenideEventListener().logScreenshots(false).logPageSources(false)); + } +} +``` +This disables both: screenshot and page sources logging. + +#### Selenium logs + +The logger can also attach Selenium logs on step failure. To enable it you need to call specific setter method inside the listener and +bypass desired log type and level: +```java +public class BaseTest { + static { + SelenideLogger.addListener("Report Portal logger", + new ReportPortalSelenideEventListener().enableSeleniumLogs(LogType.BROWSER, Level.FINER)); + } +} +``` + +#### Step name sanitizing + +If you need to hide some secret data from you step logs you can do this by specifying step name converter in logger constructor. +```java +public class BaseTest { + static { + SelenideLogger.addListener("Report Portal logger", + new ReportPortalSelenideEventListener(LogLevel.INFO, l -> l.replaceAll("secret_token=[^&]*", "secret_token="))); + } +} +``` diff --git a/README_TEMPLATE.md b/README_TEMPLATE.md index a3e0fb3..e6c1607 100644 --- a/README_TEMPLATE.md +++ b/README_TEMPLATE.md @@ -11,9 +11,9 @@ The latest version: $LATEST_VERSION. Please use `Maven Central` link above to ge ## Overview -Selenide step logger for Report Portal - -Description TBD +Selenide step logging listener for Report Portal. The listener listen for Selenide log events and send them to Report Portal as steps. +It has ability to log screenshots and page sources on failure, this is enabled by default. Also, it is possible to attach different types of +WebDriver logs on failure. ## Configuration @@ -61,11 +61,50 @@ base class for all tests: ```java public class BaseTest { static { - SelenideLogger.addListener("ReportPortal logger", new ReportPortalSelenideEventListener()); + SelenideLogger.addListener("Report Portal logger", new ReportPortalSelenideEventListener()); } } ``` ### Logger configuration -TBD +#### Screenshots and page sources + +The logger has screenshots and page sources logging enabled by default. It logs them inside the step on every failure. To disable / enable +this behavior we have separate setters methods in the logger. +E.G.: +```java +public class BaseTest { + static { + SelenideLogger.addListener("Report Portal logger", + new ReportPortalSelenideEventListener().logScreenshots(false).logPageSources(false)); + } +} +``` +This disables both: screenshot and page sources logging. + +#### Selenium logs + +The logger can also attach Selenium logs on step failure. To enable it you need to call specific setter method inside the listener and +bypass desired log type and level: +```java +public class BaseTest { + static { + SelenideLogger.addListener("Report Portal logger", + new ReportPortalSelenideEventListener().enableSeleniumLogs(LogType.BROWSER, Level.FINER)); + } +} +``` + +#### Step name sanitizing + +If you need to hide some secret data from you step logs you can do this by specifying step name converter in logger constructor. +```java +public class BaseTest { + static { + SelenideLogger.addListener("Report Portal logger", + new ReportPortalSelenideEventListener(LogLevel.INFO, l -> l.replaceAll("secret_token=[^&]*", "secret_token="))); + } +} +``` +