0

Using Cucumber-java: v7.18.1

In scenarios where soft assertions are used, screenshots are captured when softAssertions.assertAll() is invoked. To ensure screenshots are taken for each soft assertion failure, a custom assertion class extending AssertJ SoftAssertions was created, overriding the onAssertionErrorCollected() method.

While this approach successfully captures screenshots, it initially led to scenarios being marked as failed in the report, but individual steps remained marked as passed since assertAll() was called in the @After hook.
 The assertAll() method is called in the @After hook to allow the test to continue executing all steps, even if some assertions fail, thus collecting all assertion errors at the end of the scenario.

By implementing a ConcurrentEventListener, this issue was resolved by modifying the JSON report after test execution.

Code

public class CustomReportListener implements ConcurrentEventListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomReportListener.class);

    Map<String, ModifiedStepDetail> modifiedStepResults = new HashMap<>();

    @Override
    public void setEventPublisher(EventPublisher publisher) {
        publisher.registerHandlerFor(TestRunFinished.class, this::afterTestRun);
        publisher.registerHandlerFor(TestStepFinished.class, this::stepFinished);
    }

    private void stepFinished(TestStepFinished testStepFinished) {
        if (testStepFinished.getTestStep() instanceof PickleStepTestStep step) {
            String stepId = step.getId().toString();
            String stepName = step.getStep().getText();
            int stepLine = step.getStep().getLine();
            boolean hasSoftAssertionFailed = TestContext.getTestContext().hasSoftAssertionFailure(stepName);

            if (testStepFinished.getResult().getStatus() == Status.PASSED && hasSoftAssertionFailed) {
                Result modifiedResult = new Result(Status.FAILED, testStepFinished.getResult().getDuration(), new AssertionError(stepName));

                modifiedStepResults.put(stepId, new ModifiedStepDetail(modifiedResult, stepName, stepLine));

                LOGGER.info("Modified result: {}", modifiedResult);
                LOGGER.info("Step ID: {}", stepId);
                LOGGER.info("Step Name: {}", stepName);
                LOGGER.info("Step Line: {}", stepLine);
            }
            LOGGER.info("STEP FINISHED : {}", step.getStep().getText());
        }
    }

    private Map<String, ModifiedStepDetail> getModifiedStepResults() {
        return modifiedStepResults;
    }

    private void afterTestRun(TestRunFinished testRunFinished) {
        JsonReportReader reader = new JsonReportReader();
        JsonReportUpdater updater = new JsonReportUpdater();

        try {
            Path strInputFile = Paths.get(ConfigurationManager.getConfiguration().getCurrentModulePath().getAbsolutePath().toString(), "target", "cucumber-reports", "Moto_Edge_30", "Moto_Edge_30.json");
            File inputFile = new File(String.valueOf(strInputFile));

            Path fileNameOutputFile = Paths.get(ConfigurationManager.getConfiguration().getCurrentModulePath().getAbsolutePath().toString(), "target", "cucumber-reports", "Moto_Edge_30", "Moto_Edge_30.json");

            File outputFile = new File(String.valueOf(fileNameOutputFile));
            JsonNode existingReport = reader.readJsonFile(inputFile);
            updater.updateJsonWithModifiedResults(existingReport, getModifiedStepResults());
            updater.writeJsonFile(existingReport, outputFile);

            LOGGER.info("Modified JSON report updated after test case finished.");

        } catch (IOException e) {
            LOGGER.error("Error updating JSON report: ", e);
        }
    }

    record ModifiedStepDetail(Result result, String stepName, int stepLine) {
    }

    static class JsonReportReader {
        private final ObjectMapper objectMapper = new ObjectMapper();

        public JsonNode readJsonFile(File file) throws IOException {
            return objectMapper.readTree(file);
        }
    }

    static class JsonReportUpdater {
        private final ObjectMapper objectMapper = new ObjectMapper();

        public void updateJsonWithModifiedResults(JsonNode rootNode, Map<String, ModifiedStepDetail> modifiedStepResults) {
            if (rootNode.isArray()) {
                rootNode.forEach(feature -> {
                    if (feature.has("elements")) {
                        feature.get("elements").forEach(scenario -> {
                            if (scenario.has("steps")) {
                                // Convert steps to a parallel stream for processing
                                scenario.get("steps").forEach(step -> {
                                    String stepName = step.get("name").asText();
                                    int stepLine = step.get("line").asInt();

                                    modifiedStepResults.values().parallelStream()
                                            .filter(modifiedStepDetail -> modifiedStepDetail.stepLine() == stepLine && modifiedStepDetail.stepName().equals(stepName))
                                            .forEach(modifiedStepDetail -> {
                                                // Ensure thread safety when modifying JSON nodes
                                                synchronized (step) {
                                                    ObjectNode resultNode = (ObjectNode) step.get("result");
                                                    resultNode.put("status", "failed");
                                                    LOGGER.info("Updated step: {} at line: {} to failed.", stepName, stepLine);
                                                }
                                            });
                                });
                            }
                        });
                    }
                });
            }
        }

        public void writeJsonFile(JsonNode jsonNode, File outputFile) throws IOException {
            objectMapper.writerWithDefaultPrettyPrinter().writeValue(outputFile, jsonNode);
        }
    }
}

Problem Statement:

How can I dynamically modify step statuses in Cucumber reports during runtime as tests are executed, instead of modifying the JSON report after the test execution is complete?

I tried, but don't know how to proceed after this.

Result modifiedResult = new Result(Status.FAILED, testStepFinished.getResult().getDuration(), new AssertionError("Soft assertion failure in step: " + stepName));
TestStepFinished modifiedEvent = new TestStepFinished(
                        testStepFinished.getInstant(),
                        testStepFinished.getTestCase(),
                        testStepFinished.getTestStep(),
                        modifiedResult
);

// Log the modification for debugging
LOGGER.info("Modified step result: {}", modifiedResult);
3
  • It is not possible. Commented Sep 14, 2024 at 11:27
  • @M.P.Korstanje Thank you for the clarification. If it's not possible to modify the step statuses dynamically during runtime, could you suggest alternative approaches or best practices for handling soft assertion failures in Cucumber? I'm looking for ways to ensure the correct reporting of step statuses when using soft assertions. Your guidance on how to best manage this scenario would be greatly appreciated. Commented Sep 15, 2024 at 0:50
  • Well, what is the underlying problem you want to solve? It sounds like your reporting tool doesn't include the results from hooks. Commented Sep 15, 2024 at 14:20

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.