Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.util.Collections;
import java.util.EnumMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;

import static io.qameta.allure.util.ResultsUtils.getStatus;
Expand All @@ -55,6 +60,7 @@ public class AllureSelenide implements LogEventListener {
private boolean savePageHtml = true;
private boolean includeSelenideLocatorsSteps = true;
private final Map<LogType, Level> logTypesToSave = new EnumMap<>(LogType.class);
private final Set<Throwable> capturedErrors = Collections.newSetFromMap(new WeakHashMap<>());
private final AllureLifecycle lifecycle;

/**
Expand Down Expand Up @@ -167,52 +173,71 @@ public void beforeEvent(final LogEvent event) {
}
}

private boolean shouldCaptureAttachments(final Throwable error) {
if (Objects.isNull(error)) {
return true;
}
final Set<Throwable> visited = Collections.newSetFromMap(new IdentityHashMap<>());
for (Throwable current = error; current != null && visited.add(current); current = current.getCause()) {
if (capturedErrors.contains(current)) {
return false;
}
}
capturedErrors.add(error);
return true;
}

private void captureAttachmentsOnFailure(final LogEvent event) {
if (!event.getStatus().equals(LogEvent.EventStatus.FAIL) || !shouldCaptureAttachments(event.getError())) {
return;
}
lifecycle.getCurrentExecutableKey().ifPresent(owner -> {
if (saveScreenshots) {
getScreenshotBytes()
.ifPresent(
bytes -> lifecycle.addAttachment(
owner,
"Screenshot",
"image/png",
new ByteArrayInputStream(bytes),
AttachmentOptions.empty()
)
);
}
if (savePageHtml) {
getPageSourceBytes()
.ifPresent(
bytes -> lifecycle.addAttachment(
owner,
"Page source",
"text/html",
new ByteArrayInputStream(bytes),
AttachmentOptions.empty()
)
);
}
if (!logTypesToSave.isEmpty()) {
logTypesToSave
.forEach((logType, level) -> {
final byte[] content = getBrowserLogs(logType, level).getBytes(UTF_8);
lifecycle.addAttachment(
owner,
"Logs from: " + logType,
"application/json",
new ByteArrayInputStream(content),
AttachmentOptions.withFileExtension(".txt")
);
});
}
});
}

/**
* {@inheritDoc}
*/
@Override
public void afterEvent(final LogEvent event) {
if (event.getStatus().equals(LogEvent.EventStatus.FAIL)) {
lifecycle.getCurrentExecutableKey().ifPresent(owner -> {
if (saveScreenshots) {
getScreenshotBytes()
.ifPresent(
bytes -> lifecycle.addAttachment(
owner,
"Screenshot",
"image/png",
new ByteArrayInputStream(bytes),
AttachmentOptions.empty()
)
);
}
if (savePageHtml) {
getPageSourceBytes()
.ifPresent(
bytes -> lifecycle.addAttachment(
owner,
"Page source",
"text/html",
new ByteArrayInputStream(bytes),
AttachmentOptions.empty()
)
);
}
if (!logTypesToSave.isEmpty()) {
logTypesToSave
.forEach((logType, level) -> {
final byte[] content = getBrowserLogs(logType, level).getBytes(UTF_8);
lifecycle.addAttachment(
owner,
"Logs from: " + logType,
"application/json",
new ByteArrayInputStream(content),
AttachmentOptions.withFileExtension(".txt")
);
});
}
});
}
captureAttachmentsOnFailure(event);

if (stepsShouldBeLogged(event)) {
lifecycle.getCurrentExecutableKey().ifPresent(owner -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,103 @@ void shouldSupportNestedSteps() {
.containsExactly("child1", "child2", "child3");
}

@AllureFeatures.Attachments
@Test
void shouldDeduplicateAttachmentsOnNestedStepFailures() {
final WebDriver wdMock = mock(WebDriver.class, withSettings().extraInterfaces(TakesScreenshot.class));
WebDriverRunner.setWebDriver(wdMock);
doReturn("screenshot-data".getBytes(StandardCharsets.UTF_8))
.when((TakesScreenshot) wdMock).getScreenshotAs(OutputType.BYTES);

final Throwable failure = new AssertionError("element not found");

final AllureResults results = runSelenideTestContext(() -> {
final AllureSelenide selenide = new AllureSelenide()
.savePageSource(false)
.screenshots(true);
SelenideLogger.addListener(UUID.randomUUID().toString(), selenide);

final SelenideLog outerLog = SelenideLogger.beginStep("outer", "outerMethod()");
final SelenideLog innerLog = SelenideLogger.beginStep("inner", "innerMethod()");

SelenideLogger.commitStep(innerLog, failure);
SelenideLogger.commitStep(outerLog, failure);
});

final List<StepResult> steps = extractAllStepsFromResults(results);
assertThat(steps).hasSize(1);
final StepResult outerStep = steps.get(0);
assertThat(outerStep.getSteps()).hasSize(1);
final StepResult innerStep = outerStep.getSteps().get(0);

assertThat(innerStep.getAttachments()).hasSize(1);
assertThat(outerStep.getAttachments()).isEmpty();
assertThat(results.getAttachments()).hasSize(1);
}

@AllureFeatures.Attachments
@Test
void shouldDeduplicateAttachmentsWhenExceptionIsWrappedInCause() {
final WebDriver wdMock = mock(WebDriver.class, withSettings().extraInterfaces(TakesScreenshot.class));
WebDriverRunner.setWebDriver(wdMock);
doReturn("screenshot-data".getBytes(StandardCharsets.UTF_8))
.when((TakesScreenshot) wdMock).getScreenshotAs(OutputType.BYTES);

final Throwable rootCause = new AssertionError("element not found");
final Throwable wrappedError = new RuntimeException("step failed", rootCause);

final AllureResults results = runSelenideTestContext(() -> {
final AllureSelenide selenide = new AllureSelenide()
.savePageSource(false)
.screenshots(true);
SelenideLogger.addListener(UUID.randomUUID().toString(), selenide);

final SelenideLog outerLog = SelenideLogger.beginStep("outer", "outerMethod()");
final SelenideLog innerLog = SelenideLogger.beginStep("inner", "innerMethod()");

SelenideLogger.commitStep(innerLog, rootCause);
SelenideLogger.commitStep(outerLog, wrappedError);
});

final List<StepResult> steps = extractAllStepsFromResults(results);
assertThat(steps).hasSize(1);
final StepResult outerStep = steps.get(0);
assertThat(outerStep.getSteps()).hasSize(1);
final StepResult innerStep = outerStep.getSteps().get(0);

assertThat(innerStep.getAttachments()).hasSize(1);
assertThat(outerStep.getAttachments()).isEmpty();
assertThat(results.getAttachments()).hasSize(1);
}

@AllureFeatures.Attachments
@Test
void shouldAttachSeparatelyForDistinctFailures() {
final WebDriver wdMock = mock(WebDriver.class, withSettings().extraInterfaces(TakesScreenshot.class));
WebDriverRunner.setWebDriver(wdMock);
doReturn("screenshot-data".getBytes(StandardCharsets.UTF_8))
.when((TakesScreenshot) wdMock).getScreenshotAs(OutputType.BYTES);

final AllureResults results = runSelenideTestContext(() -> {
final AllureSelenide selenide = new AllureSelenide()
.savePageSource(false)
.screenshots(true);
SelenideLogger.addListener(UUID.randomUUID().toString(), selenide);

final SelenideLog step1 = SelenideLogger.beginStep("step1", "method1()");
SelenideLogger.commitStep(step1, new AssertionError("first failure"));

final SelenideLog step2 = SelenideLogger.beginStep("step2", "method2()");
SelenideLogger.commitStep(step2, new AssertionError("second failure"));
});

final List<StepResult> steps = extractAllStepsFromResults(results);
assertThat(steps).hasSize(2);
assertThat(steps.get(0).getAttachments()).hasSize(1);
assertThat(steps.get(1).getAttachments()).hasSize(1);
assertThat(results.getAttachments()).hasSize(2);
}

private static StepResult extractStepFromResults(AllureResults results) {
return results
.getTestResults().iterator().next()
Expand Down