diff --git a/allure-selenide/build.gradle.kts b/allure-selenide/build.gradle.kts index 1ff5c5605..185a9100d 100644 --- a/allure-selenide/build.gradle.kts +++ b/allure-selenide/build.gradle.kts @@ -1,10 +1,20 @@ description = "Allure Selenide Integration" -val selenideVersion = "5.1.0" +val agent: Configuration by configurations.creating + +val selenideVersion = "5.2.3" dependencies { + agent("org.aspectj:aspectjweaver") api(project(":allure-java-commons")) implementation("com.codeborne:selenide:$selenideVersion") + testImplementation("org.mockito:mockito-core") + testImplementation("org.assertj:assertj-core") + testImplementation("org.junit.jupiter:junit-jupiter-api") + testImplementation("org.slf4j:slf4j-simple") + testImplementation(project(":allure-java-commons-test")) + testImplementation(project(":allure-junit-platform")) + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") } tasks.jar { @@ -14,3 +24,10 @@ tasks.jar { )) } } + +tasks.test { + useJUnitPlatform() + doFirst { + jvmArgs("-javaagent:${agent.singleFile}") + } +} diff --git a/allure-selenide/src/main/java/io/qameta/allure/selenide/AllureSelenide.java b/allure-selenide/src/main/java/io/qameta/allure/selenide/AllureSelenide.java index 94d384b64..3ddcfec48 100644 --- a/allure-selenide/src/main/java/io/qameta/allure/selenide/AllureSelenide.java +++ b/allure-selenide/src/main/java/io/qameta/allure/selenide/AllureSelenide.java @@ -23,19 +23,28 @@ import io.qameta.allure.model.Status; import io.qameta.allure.model.StatusDetails; import io.qameta.allure.model.StepResult; -import io.qameta.allure.util.ResultsUtils; import org.openqa.selenium.OutputType; import org.openqa.selenium.TakesScreenshot; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebDriverException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; +import java.util.Optional; import java.util.UUID; +import static io.qameta.allure.util.ResultsUtils.getStatus; +import static io.qameta.allure.util.ResultsUtils.getStatusDetails; + /** * @author Artem Eroshenko. */ @SuppressWarnings("unused") public class AllureSelenide implements LogEventListener { + private static final Logger LOGGER = LoggerFactory.getLogger(AllureSelenide.class); + private boolean saveScreenshots = true; private boolean savePageHtml = true; @@ -59,41 +68,61 @@ public AllureSelenide savePageSource(final boolean savePageHtml) { return this; } - @Override - public void onEvent(final LogEvent event) { - lifecycle.getCurrentTestCase().ifPresent(uuid -> { - final String stepUUID = UUID.randomUUID().toString(); - lifecycle.startStep(stepUUID, new StepResult() - .setName(event.toString()) - .setStatus(Status.PASSED)); - - lifecycle.updateStep(stepResult -> stepResult.setStart(stepResult.getStart() - event.getDuration())); - - if (LogEvent.EventStatus.FAIL.equals(event.getStatus())) { - if (saveScreenshots) { - lifecycle.addAttachment("Screenshot", "image/png", "png", getScreenshotBytes()); - } - if (savePageHtml) { - lifecycle.addAttachment("Page source", "text/html", "html", getPageSourceBytes()); - } - lifecycle.updateStep(stepResult -> { - final StatusDetails details = ResultsUtils.getStatusDetails(event.getError()) - .orElse(new StatusDetails()); - stepResult.setStatus(ResultsUtils.getStatus(event.getError()).orElse(Status.BROKEN)); - stepResult.setStatusDetails(details); - }); - } - lifecycle.stopStep(stepUUID); - }); + private static Optional getScreenshotBytes() { + try { + return Optional.of((TakesScreenshot) WebDriverRunner.getWebDriver()) + .map(wd -> wd.getScreenshotAs(OutputType.BYTES)); + } catch (WebDriverException e) { + LOGGER.warn("Could not get screen shot", e); + return Optional.empty(); + } } - - private static byte[] getScreenshotBytes() { - return ((TakesScreenshot) WebDriverRunner.getWebDriver()).getScreenshotAs(OutputType.BYTES); + private static Optional getPageSourceBytes() { + try { + return Optional.of(WebDriverRunner.getWebDriver()) + .map(WebDriver::getPageSource) + .map(ps -> ps.getBytes(StandardCharsets.UTF_8)); + } catch (WebDriverException e) { + LOGGER.warn("Could not get page source", e); + return Optional.empty(); + } } - private static byte[] getPageSourceBytes() { - return WebDriverRunner.getWebDriver().getPageSource().getBytes(StandardCharsets.UTF_8); + @Override + public void beforeEvent(final LogEvent event) { + lifecycle.getCurrentTestCaseOrStep().ifPresent(parentUuid -> { + final String uuid = UUID.randomUUID().toString(); + lifecycle.startStep(parentUuid, uuid, new StepResult().setName(event.toString())); + }); } + @Override + public void afterEvent(final LogEvent event) { + lifecycle.getCurrentTestCaseOrStep().ifPresent(parentUuid -> { + switch (event.getStatus()) { + case PASS: + lifecycle.updateStep(step -> step.setStatus(Status.PASSED)); + break; + case FAIL: + if (saveScreenshots) { + getScreenshotBytes() + .ifPresent(bytes -> lifecycle.addAttachment("Screenshot", "image/png", "png", bytes)); + } + if (savePageHtml) { + getPageSourceBytes() + .ifPresent(bytes -> lifecycle.addAttachment("Page source", "text/html", "html", bytes)); + } + lifecycle.updateStep(stepResult -> { + stepResult.setStatus(getStatus(event.getError()).orElse(Status.BROKEN)); + stepResult.setStatusDetails(getStatusDetails(event.getError()).orElse(new StatusDetails())); + }); + break; + default: + LOGGER.warn("Step finished with unsupported status {}", event.getStatus()); + break; + } + lifecycle.stopStep(); + }); + } } diff --git a/allure-selenide/src/test/java/io/qameta/allure/selenide/AllureSelenideTest.java b/allure-selenide/src/test/java/io/qameta/allure/selenide/AllureSelenideTest.java new file mode 100644 index 000000000..53164adb0 --- /dev/null +++ b/allure-selenide/src/test/java/io/qameta/allure/selenide/AllureSelenideTest.java @@ -0,0 +1,241 @@ +/* + * Copyright 2019 Qameta Software OÜ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.selenide; + +import com.codeborne.selenide.WebDriverRunner; +import com.codeborne.selenide.logevents.LogEvent; +import com.codeborne.selenide.logevents.SelenideLog; +import com.codeborne.selenide.logevents.SelenideLogger; +import io.qameta.allure.Allure; +import io.qameta.allure.model.Attachment; +import io.qameta.allure.model.Status; +import io.qameta.allure.model.StepResult; +import io.qameta.allure.test.AllureFeatures; +import io.qameta.allure.test.AllureResults; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.OutputType; +import org.openqa.selenium.chrome.ChromeDriver; + +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +import static io.qameta.allure.test.RunUtils.runWithinTestContext; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +/** + * @author charlie (Dmitry Baev). + */ +class AllureSelenideTest { + + @AllureFeatures.Steps + @Test + void shouldLogPassedSteps() { + final AllureResults results = runWithinTestContext(() -> { + final AllureSelenide selenide = new AllureSelenide() + .savePageSource(false) + .screenshots(false); + SelenideLogger.addListener(UUID.randomUUID().toString(), selenide); + final SelenideLog log = SelenideLogger.beginStep( + "dummy source", + "dummyMethod()", + "param1", + "param2" + ); + SelenideLogger.commitStep(log, LogEvent.EventStatus.PASS); + }); + + final StepResult selenideStep = extractStepFromResults(results); + assertThat(selenideStep) + .extracting(StepResult::getName, StepResult::getStatus) + .containsExactly( + "$(dummy source) dummy method()([param1, param2])", + Status.PASSED + ); + } + + @AllureFeatures.Steps + @Test + void shouldLogStepTimings() { + final AllureResults results = runWithinTestContext(() -> { + final AllureSelenide selenide = new AllureSelenide() + .savePageSource(false) + .screenshots(false); + SelenideLogger.addListener(UUID.randomUUID().toString(), selenide); + final SelenideLog log = SelenideLogger.beginStep( + "dummy source", + "dummyMethod()", + "param1", + "param2" + ); + SelenideLogger.commitStep(log, LogEvent.EventStatus.PASS); + }); + + final StepResult selenideStep = extractStepFromResults(results); + assertThat(selenideStep) + .as("start timestamp") + .extracting(StepResult::getStart) + .isNotNull(); + + assertThat(selenideStep) + .as("stop timestamp") + .extracting(StepResult::getStop) + .isNotNull(); + } + + @AllureFeatures.Attachments + @Test + void shouldSaveScreenshotsOnFail() { + final ChromeDriver wdMock = mock(ChromeDriver.class); + WebDriverRunner.setWebDriver(wdMock); + doReturn("hello".getBytes(StandardCharsets.UTF_8)) + .when(wdMock).getScreenshotAs(OutputType.BYTES); + + final AllureResults results = runWithinTestContext(() -> { + final AllureSelenide selenide = new AllureSelenide() + .savePageSource(false) + .screenshots(true); + SelenideLogger.addListener(UUID.randomUUID().toString(), selenide); + final SelenideLog log = SelenideLogger.beginStep( + "dummy source", + "dummyMethod()", + "param1", + "param2" + ); + SelenideLogger.commitStep(log, new Exception("something went wrong")); + }); + + final StepResult selenideStep = extractStepFromResults(results); + assertThat(selenideStep.getAttachments()) + .hasSize(1); + + final Attachment attachment = selenideStep.getAttachments().iterator().next(); + assertThat(results.getAttachments()) + .containsKey(attachment.getSource()); + + final String attachmentContent = new String( + results.getAttachments().get(attachment.getSource()), + StandardCharsets.UTF_8 + ); + + assertThat(attachmentContent) + .isEqualTo("hello"); + } + + @AllureFeatures.Attachments + @Test + void shouldSavePageSourceOnFail() { + final ChromeDriver wdMock = mock(ChromeDriver.class); + WebDriverRunner.setWebDriver(wdMock); + doReturn("dummy-page-source") + .when(wdMock).getPageSource(); + + final AllureResults results = runWithinTestContext(() -> { + final AllureSelenide selenide = new AllureSelenide() + .screenshots(false) + .savePageSource(true); + SelenideLogger.addListener(UUID.randomUUID().toString(), selenide); + final SelenideLog log = SelenideLogger.beginStep( + "dummy source", + "dummyMethod()", + "param1", + "param2" + ); + SelenideLogger.commitStep(log, new Exception("something went wrong")); + }); + + final StepResult selenideStep = extractStepFromResults(results); + assertThat(selenideStep.getAttachments()) + .hasSize(1); + + final Attachment attachment = selenideStep.getAttachments().iterator().next(); + assertThat(results.getAttachments()) + .containsKey(attachment.getSource()); + + final String attachmentContent = new String( + results.getAttachments().get(attachment.getSource()), + StandardCharsets.UTF_8 + ); + + assertThat(attachmentContent) + .isEqualTo("dummy-page-source"); + } + + @AllureFeatures.Steps + @Test + void shouldLogFailedSteps() { + final AllureResults results = runWithinTestContext(() -> { + final AllureSelenide selenide = new AllureSelenide() + .savePageSource(false) + .screenshots(false); + SelenideLogger.addListener(UUID.randomUUID().toString(), selenide); + final SelenideLog log = SelenideLogger.beginStep( + "dummy source", + "dummyMethod()", + "param1", + "param2" + ); + SelenideLogger.commitStep(log, new Exception("something went wrong")); + }); + + final StepResult selenideStep = extractStepFromResults(results); + assertThat(selenideStep) + .extracting(StepResult::getName, StepResult::getStatus) + .containsExactly( + "$(dummy source) dummy method()([param1, param2])", + Status.BROKEN + ); + + assertThat(selenideStep) + .extracting(s -> s.getStatusDetails().getMessage()) + .isEqualTo("something went wrong"); + } + + @AllureFeatures.Steps + @Test + void shouldSupportNestedSteps() { + final AllureResults results = runWithinTestContext(() -> { + final AllureSelenide selenide = new AllureSelenide() + .savePageSource(false) + .screenshots(false); + SelenideLogger.addListener(UUID.randomUUID().toString(), selenide); + final SelenideLog log = SelenideLogger.beginStep( + "dummy source", + "dummyMethod()", + "param1", + "param2" + ); + + Allure.step("child1"); + Allure.step("child2"); + Allure.step("child3"); + + SelenideLogger.commitStep(log, LogEvent.EventStatus.PASS); + }); + + final StepResult selenideStep = extractStepFromResults(results); + assertThat(selenideStep.getSteps()) + .extracting(StepResult::getName) + .containsExactly("child1", "child2", "child3"); + } + + private static StepResult extractStepFromResults(AllureResults results) { + return results + .getTestResults().iterator().next() + .getSteps().iterator().next(); + } +} diff --git a/allure-selenide/src/test/resources/allure.properties b/allure-selenide/src/test/resources/allure.properties new file mode 100644 index 000000000..9c0b0a2d7 --- /dev/null +++ b/allure-selenide/src/test/resources/allure.properties @@ -0,0 +1,2 @@ +allure.results.directory=build/allure-results +allure.label.epic=#project.description# diff --git a/build.gradle.kts b/build.gradle.kts index ca90e5138..0f9732ac8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -147,6 +147,7 @@ configure(subprojects) { pmdVersion = "6.11.0" spotbugsVersion = "3.1.11" codenarcVersion = "1.3" + enabled = !project.hasProperty("disableQuality") } configure {