All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.selcukes.extent.report.SelcukesExtentAdapter Maven / Gradle / Ivy

The newest version!
/*
 *
 *  Copyright (c) Ramesh Babu Prudhvi.
 *
 *  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.github.selcukes.extent.report;

import com.aventstack.extentreports.ExtentReports;
import com.aventstack.extentreports.ExtentTest;
import com.aventstack.extentreports.GherkinKeyword;
import com.aventstack.extentreports.MediaEntityBuilder;
import com.aventstack.extentreports.Status;
import com.aventstack.extentreports.gherkin.model.Asterisk;
import com.aventstack.extentreports.gherkin.model.ScenarioOutline;
import com.aventstack.extentreports.markuputils.MarkupHelper;
import com.aventstack.extentreports.model.Test;
import io.cucumber.messages.types.Examples;
import io.cucumber.messages.types.Feature;
import io.cucumber.messages.types.Scenario;
import io.cucumber.messages.types.Step;
import io.cucumber.messages.types.TableCell;
import io.cucumber.messages.types.TableRow;
import io.cucumber.messages.types.Tag;
import io.cucumber.plugin.ConcurrentEventListener;
import io.cucumber.plugin.event.DataTableArgument;
import io.cucumber.plugin.event.DocStringArgument;
import io.cucumber.plugin.event.EmbedEvent;
import io.cucumber.plugin.event.EventHandler;
import io.cucumber.plugin.event.EventPublisher;
import io.cucumber.plugin.event.HookTestStep;
import io.cucumber.plugin.event.PickleStepTestStep;
import io.cucumber.plugin.event.Result;
import io.cucumber.plugin.event.StepArgument;
import io.cucumber.plugin.event.TestCase;
import io.cucumber.plugin.event.TestCaseStarted;
import io.cucumber.plugin.event.TestRunFinished;
import io.cucumber.plugin.event.TestSourceRead;
import io.cucumber.plugin.event.TestStepFinished;
import io.cucumber.plugin.event.TestStepStarted;
import io.cucumber.plugin.event.WriteEvent;
import io.github.selcukes.collections.Streams;
import io.github.selcukes.commons.helper.ExceptionHelper;
import lombok.SneakyThrows;

import java.net.URI;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import static io.github.selcukes.collections.StringHelper.isNonEmpty;
import static io.github.selcukes.extent.report.Reporter.getReporter;

public class SelcukesExtentAdapter implements ConcurrentEventListener {

    private static final Map MIME_TYPES_EXTENSIONS = Map.of(
        "image/bmp", "bmp",
        "image/gif", "gif",
        "image/jpeg", "jpeg",
        "image/jpg", "jpg",
        "image/png", "png",
        "image/svg+xml", "svg");

    private static final Map featureMap = new ConcurrentHashMap<>();
    private static final ThreadLocal featureTestThreadLocal = new InheritableThreadLocal<>();
    private static final Map scenarioOutlineMap = new ConcurrentHashMap<>();
    private static final ThreadLocal scenarioOutlineThreadLocal = new InheritableThreadLocal<>();
    private static final ThreadLocal scenarioThreadLocal = new InheritableThreadLocal<>();
    private static final ThreadLocal isHookThreadLocal = new InheritableThreadLocal<>();
    private static final ThreadLocal stepTestThreadLocal = new InheritableThreadLocal<>();
    private static final ThreadLocal> featureTagsThreadLocal = new InheritableThreadLocal<>();
    private static final ThreadLocal> scenarioOutlineTagsThreadLocal = new InheritableThreadLocal<>();
    private final TestSourcesModel testSources = new TestSourcesModel();

    private final ThreadLocal currentFeatureFile = new InheritableThreadLocal<>();
    private final ThreadLocal currentScenarioOutline = new InheritableThreadLocal<>();
    private final ThreadLocal currentExamples = new InheritableThreadLocal<>();

    private final EventHandler testSourceReadHandler = this::handleTestSourceRead;
    private final EventHandler caseStartedHandler = this::handleTestCaseStarted;
    private final EventHandler stepStartedHandler = this::handleTestStepStarted;
    private final EventHandler stepFinishedHandler = this::handleTestStepFinished;
    private final EventHandler embedEventHandler = this::handleEmbed;
    private final EventHandler writeEventHandler = this::handleWrite;
    private final EventHandler runFinishedHandler = this::handleTestRunFinished;
    private final ExtentService extentService;

    public SelcukesExtentAdapter() {

        extentService = new ExtentService(new ExtentReports());
    }

    public static synchronized void addTestStepLog(final String message) {
        if (isNonEmpty(message)) {
            stepTestThreadLocal.get().info(message);
        }
    }

    public static synchronized void attachVideo(byte[] video) {
        stepTestThreadLocal.get().addVideoFromBase64String(Base64.getEncoder().encodeToString(video));
    }

    public static void attachScreenshot(byte[] screenshot) {

        stepTestThreadLocal.get().addScreenCaptureFromBase64String(Base64.getEncoder().encodeToString(screenshot));
    }

    @Override
    public void setEventPublisher(final EventPublisher publisher) {
        publisher.registerHandlerFor(TestSourceRead.class, testSourceReadHandler);
        publisher.registerHandlerFor(TestCaseStarted.class, caseStartedHandler);
        publisher.registerHandlerFor(TestStepStarted.class, stepStartedHandler);
        publisher.registerHandlerFor(TestStepFinished.class, stepFinishedHandler);
        publisher.registerHandlerFor(EmbedEvent.class, embedEventHandler);
        publisher.registerHandlerFor(WriteEvent.class, writeEventHandler);
        publisher.registerHandlerFor(TestRunFinished.class, runFinishedHandler);
    }

    private void handleTestSourceRead(TestSourceRead event) {
        testSources.addTestSourceReadEvent(event.getUri(), event);
    }

    private synchronized void handleTestCaseStarted(TestCaseStarted event) {
        getReporter().start();
        handleStartOfFeature(event.getTestCase());
        handleScenarioOutline(event.getTestCase());
        createTestCase(event.getTestCase());
    }

    private synchronized void handleTestStepStarted(TestStepStarted event) {
        getReporter().attachAndRestart();
        isHookThreadLocal.set(false);

        if (event.getTestStep() instanceof HookTestStep hookTestStep) {
            ExtentTest t = scenarioThreadLocal.get().createNode(Asterisk.class, event.getTestStep().getCodeLocation(),
                hookTestStep.getHookType().toString().toUpperCase());
            stepTestThreadLocal.set(t);
            isHookThreadLocal.set(true);
        }

        if (event.getTestStep() instanceof PickleStepTestStep testStep) {
            createTestStep(testStep);
        }
    }

    private synchronized void handleTestStepFinished(TestStepFinished event) {
        getReporter().attachAndRestart();
        updateResult(event.getResult());
        if (!event.getResult().getStatus().isOk() && event.getResult().getError() != null) {
            ExceptionHelper.logError(event.getResult().getError());
        }

    }

    private synchronized void updateResult(Result result) {
        Test test = stepTestThreadLocal.get().getModel();
        String status = result.getStatus().name().toLowerCase();

        switch (status) {
            case "failed", "pending" -> handleFailedOrPendingResult(result);
            case "undefined" -> handleUndefinedResult();
            case "skipped" -> handleSkippedResult(result, test);
            case "passed" -> handlePassedResult(test);
            default -> {
                // do nothing
            }
        }
    }

    private void handleFailedOrPendingResult(Result result) {
        stepTestThreadLocal.get().fail(result.getError());
    }

    private void handleUndefinedResult() {
        stepTestThreadLocal.get().fail("Step undefined");
    }

    private void handleSkippedResult(Result result, Test test) {
        if (isHookThreadLocal.get().equals(Boolean.TRUE)) {
            extentService.getExtentReports().removeTest(stepTestThreadLocal.get());
            return;
        }

        boolean currentEndingEventSkipped = test.hasLog()
                && test.getLogs().get(test.getLogs().size() - 1).getStatus() == Status.SKIP;
        if (result.getError() != null) {
            stepTestThreadLocal.get().skip(result.getError());
        }
        if (!currentEndingEventSkipped) {
            String details = result.getError() == null ? "Step skipped" : result.getError().getMessage();
            stepTestThreadLocal.get().skip(details);
        }
    }

    private void handlePassedResult(Test test) {
        if (stepTestThreadLocal.get() != null) {
            if (isHookThreadLocal.get().equals(Boolean.TRUE)) {
                boolean mediaLogs = test.getLogs().stream().anyMatch(l -> l.getMedia() != null);
                if (!test.hasLog() && !mediaLogs) {
                    extentService.getExtentReports().removeTest(stepTestThreadLocal.get());
                }
            }
            stepTestThreadLocal.get().pass("");
        }
    }

    private synchronized void handleEmbed(EmbedEvent event) {

        String mimeType = event.getMediaType();
        String extension = MIME_TYPES_EXTENSIONS.get(mimeType);
        if (extension != null) {
            if (stepTestThreadLocal.get() == null) {
                ExtentTest t = scenarioThreadLocal.get().createNode(Asterisk.class, "Embed");
                stepTestThreadLocal.set(t);
            }

            String title = event.getName() == null ? "" : event.getName();
            if (mimeType.startsWith("image/")) {
                stepTestThreadLocal.get().info(title, MediaEntityBuilder
                        .createScreenCaptureFromBase64String(Base64.getEncoder().encodeToString(event.getData()))
                        .build());
            }
        }
    }

    private void handleWrite(WriteEvent event) {
        String text = event.getText();
        if (text != null && !text.isEmpty()) {
            stepTestThreadLocal.get().info(text);
        }
    }

    private void handleTestRunFinished(TestRunFinished testRunFinished) {
        finishReport();
        getReporter().removeReporter();
        featureTestThreadLocal.remove();
        scenarioThreadLocal.remove();
        isHookThreadLocal.remove();
        stepTestThreadLocal.remove();
        featureTagsThreadLocal.remove();
        scenarioOutlineTagsThreadLocal.remove();
        currentFeatureFile.remove();
    }

    private void finishReport() {
        getReporter().attachAndClear();
        extentService.getExtentReports().flush();
    }

    private synchronized void handleStartOfFeature(TestCase testCase) {
        if (!testCase.getUri().equals(currentFeatureFile.get())) {
            Objects.requireNonNull(currentFeatureFile).set(testCase.getUri());
            createFeature(testCase);
        }
    }

    @SneakyThrows
    private synchronized void createFeature(TestCase testCase) {
        Feature feature = testSources.getFeature(testCase.getUri());

        extentService.getExtentReports().setGherkinDialect(Objects.requireNonNull(feature).getLanguage());

        if (featureMap.containsKey(feature.getName())) {
            featureTestThreadLocal.set(featureMap.get(feature.getName()));
            return;
        }
        if (featureTestThreadLocal.get() != null
                && featureTestThreadLocal.get().getModel().getName().equals(feature.getName())) {
            return;
        }
        ExtentTest t = extentService.getExtentReports().createTest(
            com.aventstack.extentreports.gherkin.model.Feature.class, feature.getName(),
            feature.getDescription());
        featureTestThreadLocal.set(t);
        featureMap.put(feature.getName(), t);

        Set tagList = feature.getTags().stream().map(Tag::getName).collect(Collectors.toSet());
        featureTagsThreadLocal.set(tagList);

    }

    private synchronized void handleScenarioOutline(TestCase testCase) {
        TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile.get(),
            testCase.getLocation().getLine());
        Scenario scenarioDefinition = TestSourcesModel.getScenarioDefinition(astNode);

        if (Objects.requireNonNull(scenarioDefinition).getKeyword().equals("Scenario Outline")) {
            if (currentScenarioOutline.get() == null
                    || !currentScenarioOutline.get().getName().equals(scenarioDefinition.getName())) {
                scenarioOutlineThreadLocal.remove();
                createScenarioOutline(scenarioDefinition);
                currentScenarioOutline.set(scenarioDefinition);
            }
            Examples examples = (Examples) Objects.requireNonNull(astNode).parent.node;
            if (currentExamples.get() == null || !currentExamples.get().equals(examples)) {
                currentExamples.set(examples);
                createExamples(examples);
            }
        } else {
            scenarioOutlineThreadLocal.remove();
            currentScenarioOutline.remove();
            currentExamples.remove();
        }
    }

    private synchronized void createScenarioOutline(Scenario scenarioOutline) {
        if (scenarioOutlineMap.containsKey(scenarioOutline.getName())) {
            scenarioOutlineThreadLocal.set(scenarioOutlineMap.get(scenarioOutline.getName()));
            return;
        }
        if (scenarioOutlineThreadLocal.get() == null) {
            ExtentTest t = featureTestThreadLocal.get().createNode(
                com.aventstack.extentreports.gherkin.model.ScenarioOutline.class, scenarioOutline.getName(),
                scenarioOutline.getDescription());
            scenarioOutlineThreadLocal.set(t);
            scenarioOutlineMap.put(scenarioOutline.getName(), t);

            Set tagList = scenarioOutline.getTags().stream().map(Tag::getName)
                    .collect(Collectors.toSet());
            scenarioOutlineTagsThreadLocal.set(tagList);
        }
    }

    private void createExamples(Examples examples) {
        List rows = new ArrayList<>();
        examples.getTableHeader().ifPresent(rows::add);
        rows.addAll(examples.getTableBody());
        String[][] data = getTable(rows);
        String markup = MarkupHelper.createTable(data).getMarkup();
        if (examples.getName() != null && !examples.getName().isEmpty()) {
            markup = examples.getName() + markup;
        }
        markup = scenarioOutlineThreadLocal.get().getModel().getDescription() + markup;
        scenarioOutlineThreadLocal.get().getModel().setDescription(markup);
    }

    private String[][] getTable(List rows) {
        return rows.stream().map(row -> row.getCells().stream()
                .map(TableCell::getValue).toArray(String[]::new))
                .toArray(String[][]::new);
    }

    private synchronized void createTestCase(TestCase testCase) {
        TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile.get(),
            testCase.getLocation().getLine());
        if (astNode != null) {
            Scenario scenarioDefinition = TestSourcesModel.getScenarioDefinition(astNode);
            ExtentTest parent = scenarioOutlineThreadLocal.get() != null ? scenarioOutlineThreadLocal.get()
                    : featureTestThreadLocal.get();
            ExtentTest t = parent.createNode(com.aventstack.extentreports.gherkin.model.Scenario.class,
                testCase.getName(), Objects.requireNonNull(scenarioDefinition).getDescription());
            scenarioThreadLocal.set(t);
        }
        if (!testCase.getTags().isEmpty()) {
            testCase.getTags().forEach(x -> scenarioThreadLocal.get().assignCategory(x));
        }
        if (featureTagsThreadLocal.get() != null) {
            featureTagsThreadLocal.get().forEach(x -> scenarioThreadLocal.get().assignCategory(x));
        }

        Test parent = scenarioThreadLocal.get().getModel().getParent();
        if (parent.getBddType() == ScenarioOutline.class && scenarioOutlineTagsThreadLocal.get() != null) {
            scenarioOutlineTagsThreadLocal.get().forEach(x -> scenarioThreadLocal.get().assignCategory(x));
        }
    }

    @SneakyThrows
    private synchronized void createTestStep(PickleStepTestStep testStep) {
        String stepName = testStep.getStep().getText();
        TestSourcesModel.AstNode astNode = testSources.getAstNode(currentFeatureFile.get(),
            testStep.getStep().getLine());
        if (astNode != null) {
            Step step = (Step) astNode.node;

            String name = stepName == null || stepName.isEmpty()
                    ? step.getText().replace("<", "<").replace(">", ">")
                    : stepName;
            ExtentTest t = scenarioThreadLocal.get().createNode(new GherkinKeyword(step.getKeyword().trim()),
                "" + step.getKeyword() + name + "", testStep.getCodeLocation());
            stepTestThreadLocal.set(t);

        }
        StepArgument argument = testStep.getStep().getArgument();
        if (argument != null) {
            if (argument instanceof DocStringArgument docStringArgument) {
                stepTestThreadLocal.get()
                        .pass(MarkupHelper.createCodeBlock(docStringArgument.getContent()));
            } else if (argument instanceof DataTableArgument dataTableArgument) {
                stepTestThreadLocal.get()
                        .pass(MarkupHelper.createTable(createDataTableList(dataTableArgument)));
            }
        }
    }

    private String[][] createDataTableList(DataTableArgument dataTable) {
        return Streams.toArray(dataTable.cells());
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy