Skip to content

Commit

Permalink
Merge pull request #3 from reportportal/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
HardNorth authored Jun 9, 2022
2 parents 563214c + 92dd9bb commit 4b149d9
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 25 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Changelog

## [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
Expand Down
48 changes: 43 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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=<removed>")));
}
}
```
49 changes: 44 additions & 5 deletions README_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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=<removed>")));
}
}
```

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,10 +38,20 @@

import static java.util.Optional.ofNullable;

/**
* Selenide step logging listener for Report Portal.
* <p>
* 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.
* <p>
* Basic usage:
* <pre>
* SelenideLogger.addListener("Report Portal logger", new ReportPortalSelenideEventListener());
* </pre>
*/
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<String, String> DEFAULT_LOG_CONVERTER = log -> log;
public static final Function<String, String> DEFAULT_STEP_NAME_CONVERTER = log -> log;

private static final String SCREENSHOT_MESSAGE = "Screenshot";
private static final String PAGE_SOURCE_MESSAGE = "Page source";
Expand All @@ -60,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<String, String> 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_LOG_CONVERTER);
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 <code>false</code> 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 <code>false</code> 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<? extends LogEvent> 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<? extends LogEvent> selenideLogType) {
selenideLogTypes.remove(selenideLogType);
return this;
Expand Down Expand Up @@ -130,7 +190,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()
Expand All @@ -146,7 +206,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()
Expand Down Expand Up @@ -179,7 +239,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()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<WebDriverRunner> 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<Function<String, SaveLogRQ>> logs = runEventCapture(listener::afterEvent, logEvent);
verify(stepReporter).finishPreviousStep(eq(ItemStatus.FAILED));
assertThat(logs, hasSize(2));
Expand Down Expand Up @@ -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<WebDriverRunner> driverMockedStatic = Mockito.mockStatic(WebDriverRunner.class)) {
driverMockedStatic.when(WebDriverRunner::hasWebDriverStarted).thenReturn(true);
driverMockedStatic.when(WebDriverRunner::getWebDriver).thenReturn(webDriver);

List<Function<String, SaveLogRQ>> 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<WebDriverRunner> driverMockedStatic = Mockito.mockStatic(WebDriverRunner.class)) {
driverMockedStatic.when(WebDriverRunner::hasWebDriverStarted).thenReturn(true);
driverMockedStatic.when(WebDriverRunner::getWebDriver).thenReturn(webDriver);

List<Function<String, SaveLogRQ>> 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));
}
}
}

0 comments on commit 4b149d9

Please sign in to comment.