Skip to content

Commit

Permalink
Merge pull request #21 from Invictum/20_Ability_to_push_selenium_logs
Browse files Browse the repository at this point in the history
Ability to push selenium logs
  • Loading branch information
Invictum authored Jun 20, 2018
2 parents 43454a2 + 89fdbfe commit b393ba7
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 16 deletions.
36 changes: 27 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ Setup
-------------
To add support of integration between Serenity and Report Portal simply add dependencies to your project based on used build tool.

> **Warning**
> Don't add any extra Report Portal listeners or agents. Integration is provided by single module for all availavle Serenity approaches
**Maven**

Edit project's `pom.xml` file
```
<dependency>
<groupId>com.github.invictum</groupId>
<artifactId>serenity-reportportal-integration</artifactId>
<version>1.1.1</version>
<version>1.1.3</version>
</dependency>
```
Report Portal core libraries are used, but it uses external repository, so it URL also should be added to your build configuration
Report Portal core libraries are used, but they placed in repository, so its URL also should be added to your build configuration
```
<repositories>
<repository>
Expand All @@ -39,7 +42,7 @@ Report Portal core libraries are used, but it uses external repository, so it UR

Edit `build.gradle` file in the project root
```
compile group: 'com.github.invictum', name: 'serenity-reportportal-integration', version: '1.1.1'
compile group: 'com.github.invictum', name: 'serenity-reportportal-integration', version: '1.1.3'
```
External Report Portal repository should be defined the same as for Maven
```
Expand Down Expand Up @@ -85,13 +88,28 @@ Each Serenity `TestStep` object is passed through chain of configured `StepProce
- CUSTOM

`DEFAULT` profile is used by default and contains all usually required reporting details. It generates in Report Portal a nice log that does not cluttered with extra details.
`FULL` profile contains all available `StepProcessors` and generates full reporting. To customize what should be logged `CUSTOM` profile should be used.

`FULL` profile contains all available `StepProcessors` and generates full reporting.

To customize what should be logged `CUSTOM` profile should be used.
```
StepsSetProfile config = StepsSetProfile.CUSTOM;
config.registerProcessors(new StartStepLogger(), new FinishStepLogger());
ReportIntegrationConfig.profile = config;
```
In example above `CUSTOM` profile with `StartStepLogger` and `FinishStepLogger` processors is configured. All step processors available out of the box may be observed in `com.github.invictum.reportportal.processor` package.
In example above `CUSTOM` profile with `StartStepLogger` and `FinishStepLogger` processors is configured.

**Processors**

All step processors available out of the box may be observed in `com.github.invictum.reportportal.processor` package.
For now following processors are available:
- `StartStepLogger` logs all started steps.
- `FinishStepLogger` logs all finished steps. Log level depends on step results.
- `ErrorLogger` reports error if present. Includes regular errors as well as assertion fails. It is possible to pass a boolean flag to the constructor in order to have short or long error reporting format.
- `ScreenshotAttacher` emits screenshots to RP if present. It simply attaches all available step's screenshots, so screenshot strategy is configured on Serenity level.
- `HtmlSourceAttacher` logs page source if available.
- `SeleniumLogsAttacher` reports logs supplied by Selenium. It is possible to pass predicate to constructor in order to push particular logs. By default emits all available logs.

It is possible to use integrated processors as well as implemented by your own. To make own processor implement `StepProcessor` interface. In custom implementation access to Serenity's `TestStep` object is provided
```
public class MyCustomLoggerLogger implements StepProcessor {
Expand All @@ -103,7 +121,7 @@ public class MyCustomLoggerLogger implements StepProcessor {
}
```
> **Warning**
To emit log to Report Portal time should be specified. If log timestamp is out of range of step it won't be emitted at all. `TestStep` object contains all data to calculate start, end dates and duration
> To emit log to Report Portal time should be specified. If log timestamp is out of range of step it won't be emitted at all. `TestStep` object contains all data to calculate start, end dates and duration
The order of processors registration is matters, this order the same as order of invocation.

Expand Down Expand Up @@ -147,7 +165,7 @@ Integration provides two strategies of storing Serenity's test data to Report Po
Report Portal has a few limitations regurding to flexible nested structures support for now. As a result test report may contain some inaccuracate data.
E. g. test count for launch will show total number of tests + total number of steps.

Nevertheless `TREE` configuration allows to get additional features with RP. E. g. integrated RP test analisys facility will be changed from test to step.
Nevertheless `TREE` configuration allows to get additional features with RP. E. g. integrated RP test analisys facility scope will be changed from test to step.

Handler type may be changed with following configuration
```
Expand Down Expand Up @@ -230,12 +248,12 @@ Report Portal integration uses 3 digit version format - x.y.z

Important release notes
-----------------------
Consolidated release notes are described below
Important release notes are described below. Use [releases](https://github.com/Invictum/serenity-reportportal-integration/releases) section for details regarding regular release notes.

Version | Note
---------------|---------------------------
1.0.0 - 1.0.6 | Supports RP v3 and below
1.1.0 | Minor version update due RP v4 release. Versions older than 1.1.0 are not compatible with RP v4+ and vise versa
1.1.0+ | Minor version update due RP v4 release. Versions older than 1.1.0 are not compatible with RP v4+ and vise versa

Limitations
-------------
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.github.invictum.reportportal;

import org.openqa.selenium.logging.LogEntry;

import java.util.Map;
import java.util.logging.Level;

/**
* Enhanced version of {@link LogEntry} with added type field
*/
public class EnhancedLogEntry extends LogEntry {

private String type;

public EnhancedLogEntry(String type, Level level, long timestamp, String message) {
super(level, timestamp, message);
this.type = type;
}

public String getType() {
return type;
}

@Override
public Map<String, Object> toJson() {
Map<String, Object> json = super.toJson();
json.put("type", type);
return json;
}
}
60 changes: 60 additions & 0 deletions src/main/java/com/github/invictum/reportportal/LogStorage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.github.invictum.reportportal;

import org.openqa.selenium.logging.Logs;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
* Central storage for collected logs provided by Selenium
*/
public class LogStorage {

private ThreadLocal<List<EnhancedLogEntry>> logs = ThreadLocal.withInitial(ArrayList::new);
private ThreadLocal<Optional<Set<String>>> types = ThreadLocal.withInitial(Optional::empty);

/**
* Collects {@link org.openqa.selenium.WebDriver} logs if available and stores its to internal storage
*
* @param seleniumLogs to collect from
*/
public void collect(Logs seleniumLogs) {
// Discover available log types
if (!types.get().isPresent()) {
Set<String> discoveredTypes = seleniumLogs.getAvailableLogTypes();
types.set(Optional.of(discoveredTypes));
}
// Collect all available logs
types.get().ifPresent(types -> types.forEach(type -> {
List<EnhancedLogEntry> typedLogs = seleniumLogs.get(type).getAll().stream()
.map(log -> new EnhancedLogEntry(type, log.getLevel(), log.getTimestamp(), log.getMessage()))
.collect(Collectors.toList());
logs.get().addAll(typedLogs);
}));
}

/**
* Clears all logs related to current thread
*/
public void clean() {
logs.remove();
types.remove();
}

/**
* Returns logs that meets defined condition and removes them from storage.
* Logs could be queried only once
*
* @param predicate condition to use to find logs
* @return a list of queried logs
*/
public List<EnhancedLogEntry> query(Predicate<EnhancedLogEntry> predicate) {
List<EnhancedLogEntry> result = logs.get().stream().filter(predicate).collect(Collectors.toList());
logs.get().removeAll(result);
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@
import com.github.invictum.reportportal.handler.Handler;
import com.github.invictum.reportportal.handler.HandlerType;
import com.github.invictum.reportportal.handler.TreeHandler;
import com.github.invictum.reportportal.injector.IntegrationInjector;
import net.thucydides.core.model.DataTable;
import net.thucydides.core.model.Story;
import net.thucydides.core.model.TestOutcome;
import net.thucydides.core.steps.ExecutedStepDescription;
import net.thucydides.core.steps.StepFailure;
import net.thucydides.core.steps.StepListener;
import net.thucydides.core.webdriver.ThucydidesWebDriverSupport;
import org.openqa.selenium.logging.Logs;

import java.util.Map;

public class ReportPortalListener implements StepListener {

private Handler handler;
private LogStorage logStorage;

public ReportPortalListener() {
handler = (ReportIntegrationConfig.handlerType == HandlerType.FLAT) ? new FlatHandler() : new TreeHandler();
logStorage = IntegrationInjector.getInjector().getInstance(LogStorage.class);
}

public void testSuiteStarted(Class<?> storyClass) {
Expand All @@ -38,11 +43,12 @@ public void testStarted(String description) {
}

public void testStarted(String description, String id) {
handler.startTest(description);
testStarted(description);
}

public void testFinished(TestOutcome result) {
handler.finishTest(result);
logStorage.clean();
}

public void testRetried() {
Expand All @@ -59,26 +65,32 @@ public void skippedStepStarted(ExecutedStepDescription description) {

public void stepFailed(StepFailure failure) {
/* Not used by listener */
collectDriverLogs();
}

public void lastStepFailed(StepFailure failure) {
/* Not used by listener */
collectDriverLogs();
}

public void stepIgnored() {
/* Not used by listener */
collectDriverLogs();
}

public void stepPending() {
/* Not used by listener */
collectDriverLogs();
}

public void stepPending(String message) {
/* Not used by listener */
collectDriverLogs();
}

public void stepFinished() {
/* Not used by listener */
collectDriverLogs();
}

public void testFailed(TestOutcome testOutcome, Throwable cause) {
Expand Down Expand Up @@ -128,4 +140,11 @@ public void assumptionViolated(String message) {
public void testRunFinished() {
/* Not used by listener */
}

private void collectDriverLogs() {
if (ThucydidesWebDriverSupport.isInitialised()) {
Logs logs = ThucydidesWebDriverSupport.getDriver().manage().logs();
logStorage.collect(logs);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public StepProcessor[] processors() {
new ScreenshotAttacher(),
new FinishStepLogger(),
new ErrorLogger(true),
new HtmlSourceAttacher()
new HtmlSourceAttacher(),
new SeleniumLogsAttacher()
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.github.invictum.reportportal.injector;

import com.epam.reportportal.service.Launch;
import com.github.invictum.reportportal.NarrativeFormatter;
import com.github.invictum.reportportal.ReportIntegrationConfig;
import com.github.invictum.reportportal.StepProcessorsHolder;
import com.github.invictum.reportportal.StepProcessorsHolderProvider;
import com.github.invictum.reportportal.*;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Scopes;
Expand All @@ -15,6 +12,7 @@ public class SerenityPortalModule extends AbstractModule {
protected void configure() {
bind(Launch.class).toProvider(ReportLaunchProvider.class).in(Scopes.SINGLETON);
bind(StepProcessorsHolder.class).toProvider(StepProcessorsHolderProvider.class).in(Scopes.SINGLETON);
bind(LogStorage.class).in(Scopes.SINGLETON);
}

@Provides
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.github.invictum.reportportal.processor;

import com.epam.reportportal.service.ReportPortal;
import com.github.invictum.reportportal.EnhancedLogEntry;
import com.github.invictum.reportportal.LogLevel;
import com.github.invictum.reportportal.LogStorage;
import com.github.invictum.reportportal.Utils;
import com.github.invictum.reportportal.injector.IntegrationInjector;
import net.thucydides.core.model.TestStep;

import java.util.Date;
import java.util.function.Predicate;

/**
* Attaches logs provided by Selenium
*/
public class SeleniumLogsAttacher implements StepProcessor {

private Predicate<EnhancedLogEntry> filter;

public SeleniumLogsAttacher(Predicate<EnhancedLogEntry> filter) {
this.filter = filter;
}

public SeleniumLogsAttacher() {
this(log -> true);
}

@Override
public void proceed(final TestStep step) {
long start = Utils.stepEndDate(step).getTime();
long end = Utils.stepEndDate(step).getTime();
LogStorage storage = IntegrationInjector.getInjector().getInstance(LogStorage.class);
storage.query(filter.and(entry -> start <= entry.getTimestamp() && entry.getTimestamp() >= end))
.forEach(log -> {
String message = String.format("[Selenium-%s] [%s] %s", log.getType(), log.getLevel(), log.getMessage());
ReportPortal.emitLog(message, LogLevel.DEBUG.toString(), new Date(log.getTimestamp()));
});
}

@Override
public boolean equals(Object obj) {
return obj instanceof SeleniumLogsAttacher;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.github.invictum.reportportal;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import java.util.logging.Level;

@RunWith(JUnit4.class)
public class EnhancedLogEntityTest {

@Test
public void toJsonTest() {
EnhancedLogEntry entry = new EnhancedLogEntry("type", Level.INFO, 0, "Message");
Assert.assertTrue(entry.toJson().containsKey("type"));
}
}
Loading

0 comments on commit b393ba7

Please sign in to comment.