From 125d295d5d32ba5282cc26c6b1bf250de3a4ca5b Mon Sep 17 00:00:00 2001 From: Nick Date: Sat, 9 May 2026 22:47:54 +0300 Subject: [PATCH 1/3] Issue-127 Add Build Url. Add @LinkTest. Fix @Step processing. Add step artifact upload flag. --- java-reporter-core/pom.xml | 2 +- .../io/testomat/core/annotation/LinkTest.java | 12 + .../request/NativeRequestBodyBuilder.java | 101 +++++- .../core/constants/ApiRequestFields.java | 1 + .../core/constants/CommonConstants.java | 2 +- .../core/constants/PropertyNameConstants.java | 2 + .../io/testomat/core/facade/Testomatio.java | 11 +- .../java/io/testomat/core/model/Link.java | 26 ++ .../io/testomat/core/model/TestMetadata.java | 14 +- .../io/testomat/core/model/TestResult.java | 21 +- .../interf/PropertyProvider.java | 15 + .../io/testomat/core/step/StepAspect.java | 260 +++++++------- .../request/NativeRequestBodyBuilderTest.java | 339 ++++++++---------- .../testomat/core/model/TestMetadataTest.java | 167 ++++++--- .../testomat/core/model/TestResultTest.java | 186 +++++++--- .../io/testomat/core/step/StepAspectTest.java | 65 ++++ java-reporter-cucumber/pom.xml | 4 +- java-reporter-junit/pom.xml | 4 +- .../JUnitTestResultConstructor.java | 3 +- .../extractor/JunitMetaDataExtractor.java | 24 +- .../JUnitTestResultConstructorTest.java | 61 +++- .../extractor/JunitMetaDataExtractorTest.java | 44 +++ .../junit/reporter/JunitTestReporterTest.java | 14 +- java-reporter-karate/pom.xml | 4 +- java-reporter-testng/pom.xml | 4 +- .../TestNgTestResultConstructor.java | 3 +- .../extractor/TestNgMetaDataExtractor.java | 24 +- .../TestNgTestResultConstructorTest.java | 81 +++++ .../TestNgMetaDataExtractorTest.java | 46 +++ pom.xml | 2 +- testomat-allure-adapter/pom.xml | 4 +- .../io/testomat/resolver/TestNgResolver.java | 3 +- 32 files changed, 1105 insertions(+), 444 deletions(-) create mode 100644 java-reporter-core/src/main/java/io/testomat/core/annotation/LinkTest.java create mode 100644 java-reporter-core/src/main/java/io/testomat/core/model/Link.java diff --git a/java-reporter-core/pom.xml b/java-reporter-core/pom.xml index e7162ca4..2fc4aa82 100644 --- a/java-reporter-core/pom.xml +++ b/java-reporter-core/pom.xml @@ -7,7 +7,7 @@ io.testomat java-reporter-core - 0.11.6 + 0.12.0 jar Testomat.io Reporter Core diff --git a/java-reporter-core/src/main/java/io/testomat/core/annotation/LinkTest.java b/java-reporter-core/src/main/java/io/testomat/core/annotation/LinkTest.java new file mode 100644 index 00000000..1fa1f9a9 --- /dev/null +++ b/java-reporter-core/src/main/java/io/testomat/core/annotation/LinkTest.java @@ -0,0 +1,12 @@ +package io.testomat.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface LinkTest { + String[] value() default {}; +} diff --git a/java-reporter-core/src/main/java/io/testomat/core/client/request/NativeRequestBodyBuilder.java b/java-reporter-core/src/main/java/io/testomat/core/client/request/NativeRequestBodyBuilder.java index 70a9d232..279b29e2 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/client/request/NativeRequestBodyBuilder.java +++ b/java-reporter-core/src/main/java/io/testomat/core/client/request/NativeRequestBodyBuilder.java @@ -18,15 +18,20 @@ import io.testomat.core.facade.methods.label.LabelStorage; import io.testomat.core.facade.methods.logmethod.LogStorage; import io.testomat.core.facade.methods.meta.MetaStorage; +import io.testomat.core.model.Link; import io.testomat.core.model.TestResult; import io.testomat.core.propertyconfig.impl.PropertyProviderFactoryImpl; import io.testomat.core.propertyconfig.interf.PropertyProvider; import io.testomat.core.step.TestStep; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,6 +73,10 @@ public String buildCreateRunBody(String title) { if (groupTitle != null) { body.put(ApiRequestFields.GROUP_TITLE, groupTitle); } + String buildUrl = resolveBuildUrl(); + if (buildUrl != null) { + body.put(ApiRequestFields.BUILD_URL, buildUrl); + } if (this.sharedRun != null) { body.put("shared_run", sharedRun); @@ -179,9 +188,10 @@ private Map buildTestResultMap(TestResult result) throws JsonPro body.put("rid", result.getRid()); addMeta(body, rid); addLogs(body, rid); - addLinks(body, rid); + addLinks(result, rid); } + body.put("links", result.getLinks()); body.put("overwrite", Optional.ofNullable(result.isOverwrite()).orElse(true)); Map storageEntry = new HashMap<>(); @@ -252,6 +262,68 @@ private String getPropertySafely(String propertyName) { } } + /** + * Resolves the CI/CD build URL from supported environment variables + * (Jenkins, GitLab CI, CircleCI, GitHub Actions, Azure DevOps). + * + *

Returns only valid HTTP/HTTPS URLs. + * + * @return build URL or {@code null} if unavailable or invalid + */ + private String resolveBuildUrl() { + String buildUrl = getEnv( + "BUILD_URL", // Jenkins + "CI_JOB_URL", // GitLab + "CIRCLE_BUILD_URL" // CircleCI + ); + + // GitHub Actions + if (buildUrl == null && System.getenv("GITHUB_RUN_ID") != null) { + String server = System.getenv("GITHUB_SERVER_URL"); + String repo = System.getenv("GITHUB_REPOSITORY"); + String runId = System.getenv("GITHUB_RUN_ID"); + + if (server != null && repo != null && runId != null) { + buildUrl = String.format("%s/%s/actions/runs/%s", server, repo, runId); + } + } + + // Azure DevOps + if (buildUrl == null && System.getenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI") != null) { + String collection = System.getenv("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI"); + String project = System.getenv("SYSTEM_TEAMPROJECT"); + String buildId = System.getenv("BUILD_BUILDID"); + + if (collection != null && project != null && buildId != null) { + buildUrl = String.format("%s/%s/_build/results?buildId=%s", collection, project, buildId); + } + } + + if (buildUrl != null && !(buildUrl.startsWith("http://") || buildUrl.startsWith("https://"))) { + return null; + } + + try { + if (buildUrl != null) { + new java.net.URI(buildUrl); + } + } catch (Exception e) { + return null; + } + + return buildUrl; + } + + private String getEnv(String... keys) { + for (String key : keys) { + String value = System.getenv(key); + if (value != null && !value.isEmpty()) { + return value; + } + } + return null; + } + private boolean getCreateParam() { try { return provider.getProperty(CREATE_TEST_PROPERTY_NAME).equalsIgnoreCase(TRUE); @@ -290,13 +362,30 @@ private void addMeta(Map body, String rid) { } } - private void addLinks(Map body, String rid) { - body.put("links", new ArrayList<>()); - List links = (List) body.get("links"); + private void addLinks(TestResult result, String rid) { List> labels = LabelStorage.LINKED_LABEL_STORAGE.get(rid); - if (labels != null && !labels.isEmpty()) { - links.addAll(labels); + if (labels == null || labels.isEmpty()) { + return; } + + List links = new ArrayList<>( + Optional.ofNullable(result.getLinks()) + .orElse(Collections.emptyList()) + ); + + Set existingLabels = links.stream() + .map(Link::getLabel) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + labels.stream() + .map(m -> m.get("label")) + .filter(Objects::nonNull) + .filter(existingLabels::add) + .map(Link::label) + .forEach(links::add); + + result.setLinks(links); } /** diff --git a/java-reporter-core/src/main/java/io/testomat/core/constants/ApiRequestFields.java b/java-reporter-core/src/main/java/io/testomat/core/constants/ApiRequestFields.java index 590b36b8..5c7e543b 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/constants/ApiRequestFields.java +++ b/java-reporter-core/src/main/java/io/testomat/core/constants/ApiRequestFields.java @@ -20,6 +20,7 @@ public class ApiRequestFields { public static final String DURATION = "duration"; public static final String ENVIRONMENT = "environment"; public static final String GROUP_TITLE = "group_title"; + public static final String BUILD_URL = "ci_build_url"; private ApiRequestFields() { } diff --git a/java-reporter-core/src/main/java/io/testomat/core/constants/CommonConstants.java b/java-reporter-core/src/main/java/io/testomat/core/constants/CommonConstants.java index ede8d97d..00898f23 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/constants/CommonConstants.java +++ b/java-reporter-core/src/main/java/io/testomat/core/constants/CommonConstants.java @@ -1,7 +1,7 @@ package io.testomat.core.constants; public class CommonConstants { - public static final String REPORTER_VERSION = "0.11.6"; + public static final String REPORTER_VERSION = "0.12.0"; public static final String TESTS_STRING = "tests"; public static final String API_KEY_STRING = "api_key"; diff --git a/java-reporter-core/src/main/java/io/testomat/core/constants/PropertyNameConstants.java b/java-reporter-core/src/main/java/io/testomat/core/constants/PropertyNameConstants.java index 3d662ca1..04833a9a 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/constants/PropertyNameConstants.java +++ b/java-reporter-core/src/main/java/io/testomat/core/constants/PropertyNameConstants.java @@ -17,4 +17,6 @@ public class PropertyNameConstants { public static final String SHARED_RUN_PROPERTY_NAME = "testomatio.shared.run"; public static final String SHARED_TIMEOUT_PROPERTY_NAME = "testomatio.shared.run.timeout"; + + public static final String UPLOAD_STEP_ARTIFACTS = "testomatio.step.artifacts.enabled"; } diff --git a/java-reporter-core/src/main/java/io/testomat/core/facade/Testomatio.java b/java-reporter-core/src/main/java/io/testomat/core/facade/Testomatio.java index fc1a95d4..b316ac01 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/facade/Testomatio.java +++ b/java-reporter-core/src/main/java/io/testomat/core/facade/Testomatio.java @@ -1,9 +1,13 @@ package io.testomat.core.facade; +import static io.testomat.core.constants.PropertyNameConstants.UPLOAD_STEP_ARTIFACTS; + import io.testomat.core.facade.methods.artifact.manager.ArtifactManager; import io.testomat.core.facade.methods.label.LabelStorage; import io.testomat.core.facade.methods.logmethod.LogStorage; import io.testomat.core.facade.methods.meta.MetaStorage; +import io.testomat.core.propertyconfig.impl.PropertyProviderFactoryImpl; +import io.testomat.core.propertyconfig.interf.PropertyProvider; import io.testomat.core.step.StepLifecycle; import io.testomat.core.step.StepStatus; import io.testomat.core.step.StepTimer; @@ -19,6 +23,8 @@ * Provides simple static methods for test artifact management and reporting. */ public class Testomatio { + private static final PropertyProvider provider = + PropertyProviderFactoryImpl.getPropertyProviderFactory().getPropertyProvider(); /** * Registers artifact files or directories to be uploaded for the current test. * @@ -34,9 +40,12 @@ public static void artifact(String... directories) { * @param directories artifact directories to attach (ignored if null or empty) */ public static void stepArtifact(String... directories) { + if (!provider.getBooleanProperty(UPLOAD_STEP_ARTIFACTS)) { + return; + } TestStep testStep = StepLifecycle.current(); - if(directories == null || directories.length == 0){ + if (directories == null || directories.length == 0){ return; } diff --git a/java-reporter-core/src/main/java/io/testomat/core/model/Link.java b/java-reporter-core/src/main/java/io/testomat/core/model/Link.java new file mode 100644 index 00000000..a21881a0 --- /dev/null +++ b/java-reporter-core/src/main/java/io/testomat/core/model/Link.java @@ -0,0 +1,26 @@ +package io.testomat.core.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Link { + + private final String label; + private final String test; + + private Link(String label, String test) { + this.label = label; + this.test = test; + } + + public static Link test(String test) { + return new Link(null, test); + } + + public static Link label(String label) { + return new Link(label, null); + } + + public String getLabel() { return label; } + public String getTest() { return test; } +} diff --git a/java-reporter-core/src/main/java/io/testomat/core/model/TestMetadata.java b/java-reporter-core/src/main/java/io/testomat/core/model/TestMetadata.java index 63a6ff2b..f2d358b7 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/model/TestMetadata.java +++ b/java-reporter-core/src/main/java/io/testomat/core/model/TestMetadata.java @@ -1,5 +1,7 @@ package io.testomat.core.model; +import java.util.List; + /** * Basic test identification metadata without execution results. * Contains essential information for test discovery and organization. @@ -10,6 +12,7 @@ public class TestMetadata { private String testId; private String suiteTitle; private String file; + private List links; /** * Creates test metadata with identification information. @@ -20,11 +23,12 @@ public class TestMetadata { * @param file source file path where test is located */ public TestMetadata(String title, String testId, - String suiteTitle, String file) { + String suiteTitle, String file, List links) { this.title = title; this.testId = testId; this.suiteTitle = suiteTitle; this.file = file; + this.links = links; } public String getTitle() { @@ -58,4 +62,12 @@ public String getFile() { public void setFile(String file) { this.file = file; } + + public List getLinks() { + return links; + } + + public void setLinks(List links) { + this.links = links; + } } diff --git a/java-reporter-core/src/main/java/io/testomat/core/model/TestResult.java b/java-reporter-core/src/main/java/io/testomat/core/model/TestResult.java index 14cda50c..de369744 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/model/TestResult.java +++ b/java-reporter-core/src/main/java/io/testomat/core/model/TestResult.java @@ -48,6 +48,8 @@ public class TestResult { /** Test steps executed during the test */ private List steps; + private List links; + /** Test result is overwritten when the test is executed multiple times */ private Boolean overwrite; @@ -58,7 +60,7 @@ public TestResult(String title, String testId, String suiteTitle, String file, String status, String message, String stack, Object example, String rid, Boolean overwrite, - List steps) { + List steps, List links) { this.title = title; this.testId = testId; this.suiteTitle = suiteTitle; @@ -70,6 +72,7 @@ public TestResult(String title, String testId, this.rid = rid; this.overwrite = overwrite; this.steps = steps; + this.links = links; } /** @@ -88,6 +91,7 @@ public static class Builder { private String rid; private Boolean overwrite; private List steps; + private List links; public Builder withTitle(String title) { this.title = title; @@ -144,8 +148,13 @@ public Builder withSteps(List steps) { return this; } + public Builder withLinks(List links) { + this.links = links; + return this; + } + public TestResult build() { - return new TestResult(title, testId, suiteTitle, file, status, message, stack, example, rid, overwrite, steps); + return new TestResult(title, testId, suiteTitle, file, status, message, stack, example, rid, overwrite, steps, links); } } @@ -245,4 +254,12 @@ public Boolean isOverwrite() { public void setSteps(List steps) { this.steps = steps; } + + public List getLinks() { + return links; + } + + public void setLinks(List links) { + this.links = links; + } } \ No newline at end of file diff --git a/java-reporter-core/src/main/java/io/testomat/core/propertyconfig/interf/PropertyProvider.java b/java-reporter-core/src/main/java/io/testomat/core/propertyconfig/interf/PropertyProvider.java index 30a98691..5c369824 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/propertyconfig/interf/PropertyProvider.java +++ b/java-reporter-core/src/main/java/io/testomat/core/propertyconfig/interf/PropertyProvider.java @@ -1,6 +1,7 @@ package io.testomat.core.propertyconfig.interf; import io.testomat.core.exception.PropertyNotFoundException; +import io.testomat.core.propertyconfig.util.StringUtils; /** * Property provider interface supporting chain of responsibility pattern. @@ -29,6 +30,20 @@ public interface PropertyProvider { */ String getProperty(String key); + /** + * Returns a boolean property value or {@code false} if unavailable. + * + * @param key the property key + * @return parsed boolean value + */ + default boolean getBooleanProperty(String key) { + try { + return Boolean.parseBoolean(getProperty(key)); + } catch (PropertyNotFoundException ignored) { + return false; + } + } + /** * Sets the next provider in the chain for fallback property resolution. * When this provider cannot find a property, it delegates to the next provider. diff --git a/java-reporter-core/src/main/java/io/testomat/core/step/StepAspect.java b/java-reporter-core/src/main/java/io/testomat/core/step/StepAspect.java index 7e2d6dd8..1332944a 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/step/StepAspect.java +++ b/java-reporter-core/src/main/java/io/testomat/core/step/StepAspect.java @@ -3,9 +3,10 @@ import static io.testomat.core.facade.Testomatio.stepArtifact; import io.testomat.core.annotation.Step; +import java.lang.reflect.Method; import java.util.Arrays; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.*; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; @@ -17,79 +18,146 @@ */ @Aspect public class StepAspect { + private static final Logger log = LoggerFactory.getLogger(StepAspect.class); + @Pointcut("execution(* *(..)) && @annotation(io.testomat.core.annotation.Step)") + public void stepAnnotation() {} + /** - * Intercepts method execution for methods annotated with {@link Step}. - * Captures the step name and execution duration, then creates a {@link TestStep} object - * and stores it in ThreadLocal storage for later inclusion in test reports. + * Initializes and starts a test step before execution of a method + * annotated with {@link Step}. + * + *

Creates a new {@link TestStep}, resolves the step name with + * substituted parameters, starts step lifecycle tracking, + * and records execution start time. * - * @param joinPoint the join point representing the intercepted method - * @param step the Step annotation instance - * @return the result of the intercepted method execution - * @throws Throwable if the underlying method execution fails + * @param joinPoint intercepted method invocation */ - @Around("execution(@io.testomat.core.annotation.Step * *(..)) && @annotation(step)") - public Object aroundStep(ProceedingJoinPoint joinPoint, Step step) throws Throwable { + @Before("stepAnnotation()") + public void beforeStep(JoinPoint joinPoint) { + Step step = resolveStepAnnotation(joinPoint); + String stepName = resolveStepName(joinPoint, step); + + TestStep testStep = new TestStep(); + testStep.setStepTitle(stepName); + testStep.setCategory("user"); + + String stepId = testStep.getId().toString(); + + StepLifecycle.start(testStep); + StepTimer.start(stepId); + + log.debug("Step started: {}", stepName); + } + + /** + * Finalizes a test step after successful execution of a method + * annotated with {@link Step}. + * + *

Marks the current step as passed, records execution duration, + * attaches configured artifacts, and completes the step lifecycle. + * + * @param joinPoint intercepted method invocation + */ + @AfterReturning("stepAnnotation()") + public void afterSuccess(JoinPoint joinPoint) { + Step step = resolveStepAnnotation(joinPoint); + TestStep testStep = StepLifecycle.current(); + + if (testStep == null) { + log.warn("StepLifecycle.current() is null in afterSuccess"); + return; + } + + long duration = calculateDuration(testStep.getId().toString()); String stepName = resolveStepName(joinPoint, step); String[] artifacts = resolveAttachments(step); - createTestStep(); - long startTime = System.currentTimeMillis(); - log.info("Step aspect triggered for: {}", stepName); - Object result; - try { - result = executeStepSuccessfully(joinPoint, stepName, artifacts, startTime); - } catch (Throwable e) { - handleStepFailure(stepName, artifacts, startTime, e); - throw e; - } finally { - StepLifecycle.finish(); + testStep.setStatus(StepStatus.passed); + testStep.setDuration(duration); + + if (artifacts != null) { + stepArtifact(artifacts); } - return result; + + log.debug("Step '{}' passed in {} ms", stepName, duration); + + StepLifecycle.finish(); } - private Object executeStepSuccessfully(ProceedingJoinPoint joinPoint, String stepName, String[] artifacts, long startTime) throws Throwable { - Object result = joinPoint.proceed(); - long duration = calculateDuration(startTime); + /** + * Finalizes a test step after failed execution of a method + * annotated with {@link Step}. + * + *

Marks the current step as failed, records execution duration, + * stores error details and stack trace, attaches configured artifacts, + * and completes the step lifecycle. + * + * @param joinPoint intercepted method invocation + * @param e thrown exception + */ + @AfterThrowing(pointcut = "stepAnnotation()", throwing = "e") + public void afterFailure(JoinPoint joinPoint, Throwable e) { + Step step = resolveStepAnnotation(joinPoint); + TestStep testStep = StepLifecycle.current(); - recordStep(stepName, artifacts, StepStatus.passed, duration); - log.info("Step '{}' added to storage. Total steps: {}", stepName, StepStorage.getSteps().size()); + if (testStep == null) { + log.warn("StepLifecycle.current() is null in afterFailure"); + return; + } - return result; - } + long duration = calculateDuration(testStep.getId().toString()); + String stepName = resolveStepName(joinPoint, step); + String[] artifacts = resolveAttachments(step); - private void handleStepFailure(String stepName, String[] artifacts, long startTime, Throwable e) { - long duration = calculateDuration(startTime); - log.error("Step '{}' failed after {} ms", stepName, duration, e); - TestStep testStep = recordStep(stepName, artifacts, StepStatus.failed, duration); + testStep.setStatus(StepStatus.failed); + testStep.setDuration(duration); testStep.setError(e.getMessage()); testStep.setLog(Arrays.toString(e.getStackTrace())); + + if (artifacts != null) { + stepArtifact(artifacts); + } + + log.debug("Step '{}' failed in {} ms", stepName, duration, e); + + StepLifecycle.finish(); } - private long calculateDuration(long startTime) { - return System.currentTimeMillis() - startTime; + private long calculateDuration(String stepId) { + return System.currentTimeMillis() - StepTimer.stop(stepId); } - private TestStep recordStep(String stepName, String[] artifacts, StepStatus stepStatus, long duration) { - return initTestStep(stepName, artifacts, stepStatus, duration); + private Step resolveStepAnnotation(JoinPoint joinPoint) { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + + Method method = signature.getMethod(); + + Step step = method.getAnnotation(Step.class); + if (step != null) { + return step; + } + + try { + Method realMethod = joinPoint.getTarget() + .getClass() + .getMethod(method.getName(), method.getParameterTypes()); + + return realMethod.getAnnotation(Step.class); + + } catch (NoSuchMethodException e) { + return null; + } } - /** - * Resolves the step name from the annotation value or method name. - * Supports parameter substitution using {parameterName} placeholders. - * - * @param joinPoint the join point representing the intercepted method - * @param step the Step annotation instance - * @return resolved step name with substituted parameters - */ - private String resolveStepName(ProceedingJoinPoint joinPoint, Step step) { + private String resolveStepName(JoinPoint joinPoint, Step step) { String stepName = getStepNameTemplate(joinPoint, step); return substituteParameters(stepName, joinPoint); } - private String getStepNameTemplate(ProceedingJoinPoint joinPoint, Step step) { - if (step.value() != null && !step.value().isEmpty()) { + private String getStepNameTemplate(JoinPoint joinPoint, Step step) { + if (step != null && step.value() != null && !step.value().isEmpty()) { return step.value(); } MethodSignature signature = (MethodSignature) joinPoint.getSignature(); @@ -97,104 +165,54 @@ private String getStepNameTemplate(ProceedingJoinPoint joinPoint, Step step) { } private String[] resolveAttachments(Step step) { - if (step.artifacts() != null && step.artifacts().length > 0) { + if (step != null && step.artifacts() != null && step.artifacts().length > 0) { return step.artifacts(); } return null; } /** - * Substitutes parameter placeholders in the step name with actual parameter values. - * Supports both indexed placeholders {0}, {1}, etc. and named placeholders {parameterName}. - * Indexed placeholders work in all cases, while named placeholders require compilation - * with -parameters flag or debug information. + * Substitutes parameter placeholders in the step name with actual argument values. + * Supports both indexed placeholders ({0}, {1}, etc.) and named placeholders + * ({parameterName}). + * + *

Indexed placeholders always work, while named placeholders require + * compilation with the {@code -parameters} flag or debug information. * - * @param stepName the step name template with placeholders - * @param joinPoint the join point containing method parameters - * @return step name with substituted parameter values + * @param stepName the step name template containing placeholders + * @param joinPoint the join point containing method arguments + * @return formatted step name with substituted argument values */ - private String substituteParameters(String stepName, ProceedingJoinPoint joinPoint) { + private String substituteParameters(String stepName, JoinPoint joinPoint) { Object[] parameterValues = joinPoint.getArgs(); if (parameterValues == null || parameterValues.length == 0) { return stepName; } - String result = substituteIndexedPlaceholders(stepName, parameterValues); - result = substituteNamedPlaceholders(result, joinPoint, parameterValues); - - return result; - } - - private String substituteIndexedPlaceholders(String stepName, Object[] parameterValues) { String result = stepName; + for (int i = 0; i < parameterValues.length; i++) { String placeholder = "{" + i + "}"; - String value = formatParameterValue(parameterValues[i]); + String value = format(parameterValues[i]); result = result.replace(placeholder, value); } - return result; - } - private String substituteNamedPlaceholders(String stepName, ProceedingJoinPoint joinPoint, Object[] parameterValues) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String[] parameterNames = signature.getParameterNames(); - if (parameterNames == null || parameterNames.length != parameterValues.length) { - return stepName; + if (parameterNames != null && parameterNames.length == parameterValues.length) { + for (int i = 0; i < parameterNames.length; i++) { + String placeholder = "{" + parameterNames[i] + "}"; + String value = format(parameterValues[i]); + result = result.replace(placeholder, value); + } } - String result = stepName; - for (int i = 0; i < parameterNames.length; i++) { - String placeholder = "{" + parameterNames[i] + "}"; - String value = formatParameterValue(parameterValues[i]); - result = result.replace(placeholder, value); - } return result; } - /** - * Formats a parameter value for display in step name. - * - * @param value the parameter value - * @return formatted string representation - */ - private String formatParameterValue(Object value) { - if (value == null) { - return "null"; - } - return value.toString(); - } - - /** - * Initializes and starts a new {@link TestStep}. - */ - private void createTestStep() { - TestStep testStep = new TestStep(); - StepLifecycle.start(testStep); - } - - /** - * Initializes the current test step with metadata and optional artifacts. - * - * @param stepName step name - * @param artifacts artifact directories (optional) - * @param stepStatus step execution status - * @param durationMillis step duration in milliseconds - * @return initialized test step - */ - private TestStep initTestStep(String stepName, String[] artifacts, StepStatus stepStatus, long durationMillis) { - TestStep testStep = StepLifecycle.current(); - testStep.setCategory("user"); - testStep.setStepTitle(stepName); - testStep.setStatus(stepStatus); - testStep.setDuration(durationMillis); - - if (artifacts != null) { - stepArtifact(artifacts); - } - log.debug("Step '{}' completed in {} ms", stepName, durationMillis); - - return testStep; + private String format(Object value) { + return value == null ? "null" : value.toString(); } -} +} \ No newline at end of file diff --git a/java-reporter-core/src/test/java/io/testomat/core/client/request/NativeRequestBodyBuilderTest.java b/java-reporter-core/src/test/java/io/testomat/core/client/request/NativeRequestBodyBuilderTest.java index 138eb14c..37d94a45 100644 --- a/java-reporter-core/src/test/java/io/testomat/core/client/request/NativeRequestBodyBuilderTest.java +++ b/java-reporter-core/src/test/java/io/testomat/core/client/request/NativeRequestBodyBuilderTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -14,6 +15,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.testomat.core.constants.ApiRequestFields; import io.testomat.core.constants.PropertyNameConstants; +import io.testomat.core.model.Link; import io.testomat.core.model.TestResult; import io.testomat.core.propertyconfig.impl.PropertyProviderFactoryImpl; import io.testomat.core.propertyconfig.interf.PropertyProvider; @@ -45,9 +47,10 @@ void setUp() { when(mockFactory.getPropertyProvider()).thenReturn(mockPropertyProvider); try (MockedStatic mockedStatic = - mockStatic(PropertyProviderFactoryImpl.class)) { + mockStatic(PropertyProviderFactoryImpl.class)) { + mockedStatic.when(PropertyProviderFactoryImpl::getPropertyProviderFactory) - .thenReturn(mockFactory); + .thenReturn(mockFactory); requestBodyBuilder = new NativeRequestBodyBuilder(); } @@ -55,34 +58,41 @@ void setUp() { @Test @DisplayName("Should build basic create run body with title only") - void buildCreateRunBody_BasicTitle_ShouldContainTitleField() throws Exception { + void buildCreateRunBodyBasicTitleShouldContainTitleField() throws Exception { + String title = "Test Run Title"; String result = requestBodyBuilder.buildCreateRunBody(title); assertNotNull(result); + JsonNode jsonNode = objectMapper.readTree(result); + assertEquals(title, jsonNode.get(ApiRequestFields.TITLE).asText()); } @Test @DisplayName("Should build create run body with environment property") - void buildCreateRunBody_WithEnvironment_ShouldIncludeEnvironment() throws Exception { + void buildCreateRunBodyWithEnvironmentShouldIncludeEnvironment() throws Exception { + String title = "Test Run"; String environment = "staging"; when(mockPropertyProvider.getProperty(PropertyNameConstants.ENVIRONMENT_PROPERTY_NAME)) - .thenReturn(environment); + .thenReturn(environment); try (MockedStatic mockedStatic = - mockStatic(PropertyProviderFactoryImpl.class)) { + mockStatic(PropertyProviderFactoryImpl.class)) { + mockedStatic.when(PropertyProviderFactoryImpl::getPropertyProviderFactory) - .thenReturn(mockFactory); + .thenReturn(mockFactory); requestBodyBuilder = new NativeRequestBodyBuilder(); + String result = requestBodyBuilder.buildCreateRunBody(title); JsonNode jsonNode = objectMapper.readTree(result); + assertEquals(title, jsonNode.get(ApiRequestFields.TITLE).asText()); assertEquals(environment, jsonNode.get(ApiRequestFields.ENVIRONMENT).asText()); } @@ -90,22 +100,26 @@ void buildCreateRunBody_WithEnvironment_ShouldIncludeEnvironment() throws Except @Test @DisplayName("Should build create run body with group title") - void buildCreateRunBody_WithGroupTitle_ShouldIncludeGroupTitle() throws Exception { + void buildCreateRunBodyWithGroupTitleShouldIncludeGroupTitle() throws Exception { + String title = "Test Run"; String groupTitle = "Regression Tests"; when(mockPropertyProvider.getProperty(PropertyNameConstants.RUN_GROUP_PROPERTY_NAME)) - .thenReturn(groupTitle); + .thenReturn(groupTitle); try (MockedStatic mockedStatic = - mockStatic(PropertyProviderFactoryImpl.class)) { + mockStatic(PropertyProviderFactoryImpl.class)) { + mockedStatic.when(PropertyProviderFactoryImpl::getPropertyProviderFactory) - .thenReturn(mockFactory); + .thenReturn(mockFactory); requestBodyBuilder = new NativeRequestBodyBuilder(); + String result = requestBodyBuilder.buildCreateRunBody(title); JsonNode jsonNode = objectMapper.readTree(result); + assertEquals(title, jsonNode.get(ApiRequestFields.TITLE).asText()); assertEquals(groupTitle, jsonNode.get(ApiRequestFields.GROUP_TITLE).asText()); } @@ -113,66 +127,82 @@ void buildCreateRunBody_WithGroupTitle_ShouldIncludeGroupTitle() throws Exceptio @Test @DisplayName("Should build create run body with shared run property") - void buildCreateRunBody_WithSharedRun_ShouldIncludeSharedRun() throws Exception { + void buildCreateRunBodyWithSharedRunShouldIncludeSharedRun() throws Exception { + String title = "Test Run"; - String sharedRun = "true"; when(mockPropertyProvider.getProperty(PropertyNameConstants.SHARED_RUN_PROPERTY_NAME)) - .thenReturn(sharedRun); + .thenReturn("true"); try (MockedStatic mockedStatic = - mockStatic(PropertyProviderFactoryImpl.class)) { + mockStatic(PropertyProviderFactoryImpl.class)) { + mockedStatic.when(PropertyProviderFactoryImpl::getPropertyProviderFactory) - .thenReturn(mockFactory); + .thenReturn(mockFactory); requestBodyBuilder = new NativeRequestBodyBuilder(); + String result = requestBodyBuilder.buildCreateRunBody(title); JsonNode jsonNode = objectMapper.readTree(result); - assertEquals(title, jsonNode.get(ApiRequestFields.TITLE).asText()); - assertEquals(sharedRun, jsonNode.get("shared_run").asText()); + + assertEquals("true", jsonNode.get("shared_run").asText()); } } @Test @DisplayName("Should build create run body with publish parameter") - void buildCreateRunBody_WithPublishParam_ShouldIncludeAccessEvent() throws Exception { - String title = "Test Run"; + void buildCreateRunBodyWithPublishParamShouldIncludeAccessEvent() throws Exception { when(mockPropertyProvider.getProperty(PropertyNameConstants.PUBLISH_PROPERTY_NAME)) - .thenReturn("true"); + .thenReturn("true"); try (MockedStatic mockedStatic = - mockStatic(PropertyProviderFactoryImpl.class)) { + mockStatic(PropertyProviderFactoryImpl.class)) { + mockedStatic.when(PropertyProviderFactoryImpl::getPropertyProviderFactory) - .thenReturn(mockFactory); + .thenReturn(mockFactory); requestBodyBuilder = new NativeRequestBodyBuilder(); - String result = requestBodyBuilder.buildCreateRunBody(title); + + String result = requestBodyBuilder.buildCreateRunBody("Test Run"); JsonNode jsonNode = objectMapper.readTree(result); - assertEquals(title, jsonNode.get(ApiRequestFields.TITLE).asText()); + assertEquals("publish", jsonNode.get("access_event").asText()); } } + @Test + @DisplayName("Should include overwrite=true in create run body") + void buildCreateRunBodyShouldIncludeOverwrite() throws Exception { + + String result = requestBodyBuilder.buildCreateRunBody("Run"); + + JsonNode jsonNode = objectMapper.readTree(result); + + assertEquals("true", jsonNode.get("overwrite").asText()); + } + @Test @DisplayName("Should handle null properties gracefully") - void buildCreateRunBody_NullProperties_ShouldOnlyIncludeTitle() throws Exception { - String title = "Test Run"; + void buildCreateRunBodyNullPropertiesShouldOnlyIncludeTitle() throws Exception { - when(mockPropertyProvider.getProperty(anyString())).thenThrow(new RuntimeException("Property not found")); + when(mockPropertyProvider.getProperty(anyString())) + .thenThrow(new RuntimeException("Property not found")); try (MockedStatic mockedStatic = - mockStatic(PropertyProviderFactoryImpl.class)) { + mockStatic(PropertyProviderFactoryImpl.class)) { + mockedStatic.when(PropertyProviderFactoryImpl::getPropertyProviderFactory) - .thenReturn(mockFactory); + .thenReturn(mockFactory); requestBodyBuilder = new NativeRequestBodyBuilder(); - String result = requestBodyBuilder.buildCreateRunBody(title); + + String result = requestBodyBuilder.buildCreateRunBody("Test Run"); JsonNode jsonNode = objectMapper.readTree(result); - assertEquals(title, jsonNode.get(ApiRequestFields.TITLE).asText()); + assertNull(jsonNode.get(ApiRequestFields.ENVIRONMENT)); assertNull(jsonNode.get(ApiRequestFields.GROUP_TITLE)); assertNull(jsonNode.get("shared_run")); @@ -182,22 +212,29 @@ void buildCreateRunBody_NullProperties_ShouldOnlyIncludeTitle() throws Exception @Test @DisplayName("Should build single test report body with all fields") - void buildSingleTestReportBody_AllFields_ShouldIncludeAllData() throws Exception { + void buildSingleTestReportBodyAllFieldsShouldIncludeAllData() throws Exception { + + List links = Arrays.asList( + Link.test("T-123"), + Link.label("Smoke") + ); + TestResult testResult = new TestResult.Builder() - .withTitle("Test Method Name") - .withTestId("test-123") - .withSuiteTitle("Test Suite") - .withFile("TestClass.java") - .withStatus("passed") - .withMessage("Test passed successfully") - .withStack("stack trace here") - .withExample("example data") - .withRid("rid-456") - .build(); + .withTitle("Test Method Name") + .withTestId("test-123") + .withSuiteTitle("Test Suite") + .withFile("TestClass.java") + .withStatus("passed") + .withMessage("Test passed successfully") + .withStack("stack trace here") + .withExample("example data") + .withRid("rid-456") + .withOverwrite(false) + .withLinks(links) + .build(); String result = requestBodyBuilder.buildSingleTestReportBody(testResult); - assertNotNull(result); JsonNode jsonNode = objectMapper.readTree(result); assertEquals("Test Method Name", jsonNode.get(ApiRequestFields.TITLE).asText()); @@ -209,215 +246,141 @@ void buildSingleTestReportBody_AllFields_ShouldIncludeAllData() throws Exception assertEquals("stack trace here", jsonNode.get(ApiRequestFields.STACK).asText()); assertEquals("example data", jsonNode.get("example").asText()); assertEquals("rid-456", jsonNode.get("rid").asText()); + + assertFalse(jsonNode.get("overwrite").asBoolean()); + + assertTrue(jsonNode.has("links")); + assertEquals(2, jsonNode.get("links").size()); + + JsonNode firstLink = jsonNode.get("links").get(0); + assertEquals("T-123", firstLink.get("test").asText()); + + JsonNode secondLink = jsonNode.get("links").get(1); + assertEquals("Smoke", secondLink.get("label").asText()); } @Test @DisplayName("Should build single test report body with minimal required fields") - void buildSingleTestReportBody_MinimalFields_ShouldIncludeRequiredFieldsOnly() throws Exception { + void buildSingleTestReportBodyMinimalFieldsShouldIncludeRequiredFieldsOnly() throws Exception { + TestResult testResult = new TestResult.Builder() - .withTitle("Test Method") - .withSuiteTitle("Test Suite") - .withFile("TestClass.java") - .withStatus("failed") - .build(); + .withTitle("Test Method") + .withSuiteTitle("Test Suite") + .withFile("TestClass.java") + .withStatus("failed") + .build(); String result = requestBodyBuilder.buildSingleTestReportBody(testResult); - assertNotNull(result); JsonNode jsonNode = objectMapper.readTree(result); assertEquals("Test Method", jsonNode.get(ApiRequestFields.TITLE).asText()); - assertNull(jsonNode.get(ApiRequestFields.TEST_ID)); - assertEquals("Test Suite", jsonNode.get(ApiRequestFields.SUITE_TITLE).asText()); - assertEquals("TestClass.java", jsonNode.get(ApiRequestFields.FILE).asText()); assertEquals("failed", jsonNode.get(ApiRequestFields.STATUS).asText()); - assertNull(jsonNode.get(ApiRequestFields.MESSAGE)); - assertNull(jsonNode.get(ApiRequestFields.STACK)); - assertNull(jsonNode.get("example")); - assertNull(jsonNode.get("rid")); + + assertTrue(jsonNode.get("overwrite").asBoolean()); + assertTrue(jsonNode.get("links") == null || jsonNode.get("links").isNull()); } @Test @DisplayName("Should include create parameter when configured") - void buildSingleTestReportBody_WithCreateParam_ShouldIncludeCreateField() throws Exception { + void buildSingleTestReportBodyWithCreateParamShouldIncludeCreateField() throws Exception { + when(mockPropertyProvider.getProperty(PropertyNameConstants.CREATE_TEST_PROPERTY_NAME)) - .thenReturn("true"); + .thenReturn("true"); try (MockedStatic mockedStatic = - mockStatic(PropertyProviderFactoryImpl.class)) { + mockStatic(PropertyProviderFactoryImpl.class)) { + mockedStatic.when(PropertyProviderFactoryImpl::getPropertyProviderFactory) - .thenReturn(mockFactory); + .thenReturn(mockFactory); requestBodyBuilder = new NativeRequestBodyBuilder(); TestResult testResult = new TestResult.Builder() - .withTitle("Test Method") - .withSuiteTitle("Test Suite") - .withFile("TestClass.java") - .withStatus("passed") - .build(); + .withTitle("Test Method") + .withSuiteTitle("Test Suite") + .withFile("TestClass.java") + .withStatus("passed") + .build(); String result = requestBodyBuilder.buildSingleTestReportBody(testResult); JsonNode jsonNode = objectMapper.readTree(result); + assertEquals("true", jsonNode.get("create").asText()); } } @Test @DisplayName("Should build batch test report body with multiple results") - void buildBatchTestReportBody_MultipleResults_ShouldIncludeAllResults() throws Exception { + void buildBatchTestReportBodyMultipleResultsShouldIncludeAllResults() throws Exception { + TestResult result1 = new TestResult.Builder() - .withTitle("Test 1") - .withTestId("test-1") - .withSuiteTitle("Suite 1") - .withFile("Test1.java") - .withStatus("passed") - .build(); + .withTitle("Test 1") + .withTestId("test-1") + .withSuiteTitle("Suite 1") + .withFile("Test1.java") + .withStatus("passed") + .build(); TestResult result2 = new TestResult.Builder() - .withTitle("Test 2") - .withTestId("test-2") - .withSuiteTitle("Suite 2") - .withFile("Test2.java") - .withStatus("failed") - .withMessage("Assertion failed") - .build(); - - List results = Arrays.asList(result1, result2); - String apiKey = "test-api-key"; + .withTitle("Test 2") + .withTestId("test-2") + .withSuiteTitle("Suite 2") + .withFile("Test2.java") + .withStatus("failed") + .withMessage("Assertion failed") + .build(); + + String result = requestBodyBuilder.buildBatchTestReportBody( + Arrays.asList(result1, result2), + "test-api-key" + ); - String result = requestBodyBuilder.buildBatchTestReportBody(results, apiKey); - - assertNotNull(result); JsonNode jsonNode = objectMapper.readTree(result); - assertEquals(apiKey, jsonNode.get("api_key").asText()); - assertTrue(jsonNode.has("tests")); - assertTrue(jsonNode.get("tests").isArray()); + assertEquals("test-api-key", jsonNode.get("api_key").asText()); assertEquals(2, jsonNode.get("tests").size()); - - JsonNode firstTest = jsonNode.get("tests").get(0); - assertEquals("Test 1", firstTest.get(ApiRequestFields.TITLE).asText()); - assertEquals("test-1", firstTest.get(ApiRequestFields.TEST_ID).asText()); - assertEquals("passed", firstTest.get(ApiRequestFields.STATUS).asText()); - - JsonNode secondTest = jsonNode.get("tests").get(1); - assertEquals("Test 2", secondTest.get(ApiRequestFields.TITLE).asText()); - assertEquals("failed", secondTest.get(ApiRequestFields.STATUS).asText()); - assertEquals("Assertion failed", secondTest.get(ApiRequestFields.MESSAGE).asText()); - } - - @Test - @DisplayName("Should build batch test report body with empty results list") - void buildBatchTestReportBody_EmptyResults_ShouldReturnValidJson() throws Exception { - List results = Arrays.asList(); - String apiKey = "test-api-key"; - - String result = requestBodyBuilder.buildBatchTestReportBody(results, apiKey); - - assertNotNull(result); - JsonNode jsonNode = objectMapper.readTree(result); - - assertEquals(apiKey, jsonNode.get("api_key").asText()); - assertTrue(jsonNode.has("tests")); - assertTrue(jsonNode.get("tests").isArray()); - assertEquals(0, jsonNode.get("tests").size()); } @Test @DisplayName("Should build finish run body with duration") - void buildFinishRunBody_WithDuration_ShouldIncludeStatusAndDuration() throws Exception { - float duration = 45.5f; + void buildFinishRunBodyWithDurationShouldIncludeStatusAndDuration() throws Exception { - String result = requestBodyBuilder.buildFinishRunBody(duration); + String result = requestBodyBuilder.buildFinishRunBody(45.5f); - assertNotNull(result); JsonNode jsonNode = objectMapper.readTree(result); assertEquals("finish", jsonNode.get(ApiRequestFields.STATUS_EVENT).asText()); - assertEquals(duration, jsonNode.get(ApiRequestFields.DURATION).floatValue(), 0.001); - } - - @Test - @DisplayName("Should handle zero duration") - void buildFinishRunBody_ZeroDuration_ShouldIncludeZeroDuration() throws Exception { - float duration = 0.0f; - - String result = requestBodyBuilder.buildFinishRunBody(duration); - - assertNotNull(result); - JsonNode jsonNode = objectMapper.readTree(result); - - assertEquals("finish", jsonNode.get(ApiRequestFields.STATUS_EVENT).asText()); - assertEquals(0.0f, jsonNode.get(ApiRequestFields.DURATION).floatValue(), 0.001); - } - - @Test - @DisplayName("Should handle very large duration") - void buildFinishRunBody_LargeDuration_ShouldIncludeLargeDuration() throws Exception { - float duration = 999999.99f; - - String result = requestBodyBuilder.buildFinishRunBody(duration); - - assertNotNull(result); - JsonNode jsonNode = objectMapper.readTree(result); - - assertEquals("finish", jsonNode.get(ApiRequestFields.STATUS_EVENT).asText()); - assertEquals(duration, jsonNode.get(ApiRequestFields.DURATION).floatValue(), 0.01); + assertEquals(45.5f, jsonNode.get(ApiRequestFields.DURATION).floatValue(), 0.001); } @Test @DisplayName("Should produce valid JSON for all methods") - void allMethods_ShouldProduceValidJson() throws Exception { - // Test create run body + void allMethodsShouldProduceValidJson() throws Exception { + String createRunResult = requestBodyBuilder.buildCreateRunBody("Test Run"); assertDoesNotThrow(() -> objectMapper.readTree(createRunResult)); - // Test single test report body TestResult testResult = new TestResult.Builder() - .withTitle("Test") - .withSuiteTitle("Suite") - .withFile("Test.java") - .withStatus("passed") - .build(); + .withTitle("Test") + .withSuiteTitle("Suite") + .withFile("Test.java") + .withStatus("passed") + .build(); + String singleTestResult = requestBodyBuilder.buildSingleTestReportBody(testResult); assertDoesNotThrow(() -> objectMapper.readTree(singleTestResult)); - // Test batch test report body String batchResult = requestBodyBuilder.buildBatchTestReportBody( - Arrays.asList(testResult), "api-key"); + Arrays.asList(testResult), + "api-key" + ); + assertDoesNotThrow(() -> objectMapper.readTree(batchResult)); - // Test finish run body String finishResult = requestBodyBuilder.buildFinishRunBody(30.0f); - assertDoesNotThrow(() -> objectMapper.readTree(finishResult)); - } - @Test - @DisplayName("Should handle special characters in strings") - void buildMethods_WithSpecialCharacters_ShouldEscapeProperlyInJson() throws Exception { - String specialTitle = "Test with \"quotes\" and \n newlines \t tabs"; - - // Test create run with special characters - String createResult = requestBodyBuilder.buildCreateRunBody(specialTitle); - JsonNode createNode = objectMapper.readTree(createResult); - assertEquals(specialTitle, createNode.get(ApiRequestFields.TITLE).asText()); - - // Test single test report with special characters - TestResult testResult = new TestResult.Builder() - .withTitle(specialTitle) - .withSuiteTitle("Suite with special chars: @#$%") - .withFile("Test.java") - .withStatus("failed") - .withMessage("Error: \"Something went wrong\" with special chars: <>") - .build(); - - String singleResult = requestBodyBuilder.buildSingleTestReportBody(testResult); - JsonNode singleNode = objectMapper.readTree(singleResult); - assertEquals(specialTitle, singleNode.get(ApiRequestFields.TITLE).asText()); - assertEquals("Suite with special chars: @#$%", singleNode.get(ApiRequestFields.SUITE_TITLE).asText()); - assertEquals("Error: \"Something went wrong\" with special chars: <>", - singleNode.get(ApiRequestFields.MESSAGE).asText()); + assertDoesNotThrow(() -> objectMapper.readTree(finishResult)); } } \ No newline at end of file diff --git a/java-reporter-core/src/test/java/io/testomat/core/model/TestMetadataTest.java b/java-reporter-core/src/test/java/io/testomat/core/model/TestMetadataTest.java index 3eff5f91..4c84f89f 100644 --- a/java-reporter-core/src/test/java/io/testomat/core/model/TestMetadataTest.java +++ b/java-reporter-core/src/test/java/io/testomat/core/model/TestMetadataTest.java @@ -1,8 +1,13 @@ package io.testomat.core.model; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -12,43 +17,61 @@ class TestMetadataTest { @Test @DisplayName("Should create TestMetadata with all fields") void testConstructor() { + + Link link = Link.test("T-123"); + List links = Arrays.asList(link); + TestMetadata metadata = new TestMetadata( - "Test Title", - "test-123", - "Suite Title", - "TestFile.java" + "Test Title", + "test-123", + "Suite Title", + "TestFile.java", + links ); assertEquals("Test Title", metadata.getTitle()); assertEquals("test-123", metadata.getTestId()); assertEquals("Suite Title", metadata.getSuiteTitle()); assertEquals("TestFile.java", metadata.getFile()); + + assertNotNull(metadata.getLinks()); + assertEquals(1, metadata.getLinks().size()); + assertEquals("T-123", metadata.getLinks().get(0).getTest()); } @Test @DisplayName("Should create TestMetadata with null testId") void testConstructorWithNullTestId() { + + Link link = Link.label("Smoke"); + TestMetadata metadata = new TestMetadata( - "Test Title", - null, - "Suite Title", - "TestFile.java" + "Test Title", + null, + "Suite Title", + "TestFile.java", + Arrays.asList(link) ); assertEquals("Test Title", metadata.getTitle()); assertNull(metadata.getTestId()); assertEquals("Suite Title", metadata.getSuiteTitle()); assertEquals("TestFile.java", metadata.getFile()); + + assertNotNull(metadata.getLinks()); + assertEquals("Smoke", metadata.getLinks().get(0).getLabel()); } @Test @DisplayName("Should update TestMetadata using setters") void testSetters() { + TestMetadata metadata = new TestMetadata( - "Initial Title", - "test-123", - "Initial Suite", - "InitialFile.java" + "Initial Title", + "test-123", + "Initial Suite", + "InitialFile.java", + null ); metadata.setTitle("Updated Title"); @@ -56,38 +79,52 @@ void testSetters() { metadata.setSuiteTitle("Updated Suite"); metadata.setFile("UpdatedFile.java"); + Link link = Link.test("T-999"); + metadata.setLinks(Arrays.asList(link)); + assertEquals("Updated Title", metadata.getTitle()); assertEquals("test-456", metadata.getTestId()); assertEquals("Updated Suite", metadata.getSuiteTitle()); assertEquals("UpdatedFile.java", metadata.getFile()); + + assertNotNull(metadata.getLinks()); + assertEquals(1, metadata.getLinks().size()); + assertEquals("T-999", metadata.getLinks().get(0).getTest()); } @Test @DisplayName("Should handle empty string values") void testEmptyStringValues() { + TestMetadata metadata = new TestMetadata( - "", - "", - "", - "" + "", + "", + "", + "", + Collections.emptyList() ); assertEquals("", metadata.getTitle()); assertEquals("", metadata.getTestId()); assertEquals("", metadata.getSuiteTitle()); assertEquals("", metadata.getFile()); + + assertNotNull(metadata.getLinks()); + assertTrue(metadata.getLinks().isEmpty()); } @Test @DisplayName("Should handle special characters in title") void testSpecialCharactersInTitle() { + String specialTitle = "Test with \"quotes\" and special chars: @#$%"; TestMetadata metadata = new TestMetadata( - specialTitle, - "test-123", - "Suite", - "Test.java" + specialTitle, + "test-123", + "Suite", + "Test.java", + null ); assertEquals(specialTitle, metadata.getTitle()); @@ -96,18 +133,21 @@ void testSpecialCharactersInTitle() { @Test @DisplayName("Should handle file paths with different separators") void testFilePathsWithDifferentSeparators() { + TestMetadata metadata1 = new TestMetadata( - "Test", - "test-1", - "Suite", - "src/test/java/TestFile.java" + "Test", + "test-1", + "Suite", + "src/test/java/TestFile.java", + null ); TestMetadata metadata2 = new TestMetadata( - "Test", - "test-2", - "Suite", - "src\\test\\java\\TestFile.java" + "Test", + "test-2", + "Suite", + "src\\test\\java\\TestFile.java", + null ); assertEquals("src/test/java/TestFile.java", metadata1.getFile()); @@ -117,13 +157,15 @@ void testFilePathsWithDifferentSeparators() { @Test @DisplayName("Should handle long suite titles") void testLongSuiteTitle() { + String longSuite = "com.example.project.module.package.subpackage.TestSuiteClassName"; TestMetadata metadata = new TestMetadata( - "Test", - "test-123", - longSuite, - "Test.java" + "Test", + "test-123", + longSuite, + "Test.java", + null ); assertEquals(longSuite, metadata.getSuiteTitle()); @@ -132,11 +174,13 @@ void testLongSuiteTitle() { @Test @DisplayName("Should allow updating testId from null to value") void testUpdateTestIdFromNull() { + TestMetadata metadata = new TestMetadata( - "Test", - null, - "Suite", - "Test.java" + "Test", + null, + "Suite", + "Test.java", + null ); assertNull(metadata.getTestId()); @@ -149,11 +193,13 @@ void testUpdateTestIdFromNull() { @Test @DisplayName("Should allow updating testId to null") void testUpdateTestIdToNull() { + TestMetadata metadata = new TestMetadata( - "Test", - "test-123", - "Suite", - "Test.java" + "Test", + "test-123", + "Suite", + "Test.java", + null ); assertEquals("test-123", metadata.getTestId()); @@ -162,4 +208,43 @@ void testUpdateTestIdToNull() { assertNull(metadata.getTestId()); } -} + + @Test + @DisplayName("Should allow null links") + void testNullLinks() { + + TestMetadata metadata = new TestMetadata( + "Test", + "test-123", + "Suite", + "Test.java", + null + ); + + assertNull(metadata.getLinks()); + } + + @Test + @DisplayName("Should update links") + void testUpdateLinks() { + + TestMetadata metadata = new TestMetadata( + "Test", + "test-123", + "Suite", + "Test.java", + null + ); + + Link link1 = Link.test("T-1"); + Link link2 = Link.label("Regression"); + + metadata.setLinks(Arrays.asList(link1, link2)); + + assertNotNull(metadata.getLinks()); + assertEquals(2, metadata.getLinks().size()); + + assertEquals("T-1", metadata.getLinks().get(0).getTest()); + assertEquals("Regression", metadata.getLinks().get(1).getLabel()); + } +} \ No newline at end of file diff --git a/java-reporter-core/src/test/java/io/testomat/core/model/TestResultTest.java b/java-reporter-core/src/test/java/io/testomat/core/model/TestResultTest.java index ad8a98ae..81b71fa8 100644 --- a/java-reporter-core/src/test/java/io/testomat/core/model/TestResultTest.java +++ b/java-reporter-core/src/test/java/io/testomat/core/model/TestResultTest.java @@ -3,9 +3,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import io.testomat.core.step.TestStep; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -16,22 +18,28 @@ class TestResultTest { @Test @DisplayName("Should create TestResult with constructor") void testConstructor() { + TestStep step = new TestStep(); step.setStepTitle("Step 1"); + List steps = Arrays.asList(step); + Link link = Link.test("T-123"); + List links = Arrays.asList(link); + TestResult result = new TestResult( - "Test Title", - "test-123", - "Suite Title", - "TestFile.java", - "passed", - "Success message", - "stack trace", - "example data", - "rid-456", - true, - steps + "Test Title", + "test-123", + "Suite Title", + "TestFile.java", + "passed", + "Success message", + "stack trace", + "example data", + "rid-456", + true, + steps, + links ); assertEquals("Test Title", result.getTitle()); @@ -43,29 +51,44 @@ void testConstructor() { assertEquals("stack trace", result.getStack()); assertEquals("example data", result.getExample()); assertEquals("rid-456", result.getRid()); + + assertTrue(result.isOverwrite()); + + assertNotNull(result.getSteps()); assertEquals(1, result.getSteps().size()); assertEquals("Step 1", result.getSteps().get(0).getStepTitle()); + + assertNotNull(result.getLinks()); + assertEquals(1, result.getLinks().size()); + assertEquals("T-123", result.getLinks().get(0).getTest()); } @Test @DisplayName("Should create TestResult with builder pattern") void testBuilder() { + TestStep step = new TestStep(); step.setStepTitle("Step 1"); + List steps = Arrays.asList(step); + Link link = Link.label("Smoke"); + List links = Arrays.asList(link); + TestResult result = TestResult.builder() - .withTitle("Test Title") - .withTestId("test-123") - .withSuiteTitle("Suite Title") - .withFile("TestFile.java") - .withStatus("passed") - .withMessage("Success message") - .withStack("stack trace") - .withExample("example data") - .withRid("rid-456") - .withSteps(steps) - .build(); + .withTitle("Test Title") + .withTestId("test-123") + .withSuiteTitle("Suite Title") + .withFile("TestFile.java") + .withStatus("passed") + .withMessage("Success message") + .withStack("stack trace") + .withExample("example data") + .withRid("rid-456") + .withOverwrite(true) + .withSteps(steps) + .withLinks(links) + .build(); assertEquals("Test Title", result.getTitle()); assertEquals("test-123", result.getTestId()); @@ -76,35 +99,47 @@ void testBuilder() { assertEquals("stack trace", result.getStack()); assertEquals("example data", result.getExample()); assertEquals("rid-456", result.getRid()); + + assertTrue(result.isOverwrite()); + assertNotNull(result.getSteps()); assertEquals(1, result.getSteps().size()); + + assertNotNull(result.getLinks()); + assertEquals(1, result.getLinks().size()); + assertEquals("Smoke", result.getLinks().get(0).getLabel()); } @Test @DisplayName("Should create TestResult with minimal required fields") void testBuilderMinimalFields() { + TestResult result = TestResult.builder() - .withTitle("Test Title") - .withSuiteTitle("Suite Title") - .withFile("TestFile.java") - .withStatus("passed") - .build(); + .withTitle("Test Title") + .withSuiteTitle("Suite Title") + .withFile("TestFile.java") + .withStatus("passed") + .build(); assertEquals("Test Title", result.getTitle()); assertEquals("Suite Title", result.getSuiteTitle()); assertEquals("TestFile.java", result.getFile()); assertEquals("passed", result.getStatus()); + assertNull(result.getTestId()); assertNull(result.getMessage()); assertNull(result.getStack()); assertNull(result.getExample()); assertNull(result.getRid()); assertNull(result.getSteps()); + assertNull(result.getLinks()); + assertNull(result.isOverwrite()); } @Test @DisplayName("Should use setters to modify TestResult") void testSetters() { + TestResult result = new TestResult(); result.setTitle("Updated Title"); @@ -116,11 +151,16 @@ void testSetters() { result.setStack("updated stack"); result.setExample("updated example"); result.setRid("updated-rid"); + result.setOverwrite(true); TestStep step = new TestStep(); step.setStepTitle("Updated Step"); + result.setSteps(Arrays.asList(step)); + Link link = Link.test("T-999"); + result.setLinks(Arrays.asList(link)); + assertEquals("Updated Title", result.getTitle()); assertEquals("updated-123", result.getTestId()); assertEquals("Updated Suite", result.getSuiteTitle()); @@ -130,39 +170,47 @@ void testSetters() { assertEquals("updated stack", result.getStack()); assertEquals("updated example", result.getExample()); assertEquals("updated-rid", result.getRid()); + + assertTrue(result.isOverwrite()); + assertEquals(1, result.getSteps().size()); assertEquals("Updated Step", result.getSteps().get(0).getStepTitle()); + + assertEquals(1, result.getLinks().size()); + assertEquals("T-999", result.getLinks().get(0).getTest()); } @Test @DisplayName("Should handle different test statuses") void testDifferentStatuses() { + TestResult passed = TestResult.builder() - .withTitle("Passed Test") - .withSuiteTitle("Suite") - .withFile("Test.java") - .withStatus("passed") - .build(); + .withTitle("Passed Test") + .withSuiteTitle("Suite") + .withFile("Test.java") + .withStatus("passed") + .build(); TestResult failed = TestResult.builder() - .withTitle("Failed Test") - .withSuiteTitle("Suite") - .withFile("Test.java") - .withStatus("failed") - .withMessage("Test failed") - .withStack("at Test.java:10") - .build(); + .withTitle("Failed Test") + .withSuiteTitle("Suite") + .withFile("Test.java") + .withStatus("failed") + .withMessage("Test failed") + .withStack("at Test.java:10") + .build(); TestResult skipped = TestResult.builder() - .withTitle("Skipped Test") - .withSuiteTitle("Suite") - .withFile("Test.java") - .withStatus("skipped") - .build(); + .withTitle("Skipped Test") + .withSuiteTitle("Suite") + .withFile("Test.java") + .withStatus("skipped") + .build(); assertEquals("passed", passed.getStatus()); assertEquals("failed", failed.getStatus()); assertEquals("skipped", skipped.getStatus()); + assertNotNull(failed.getMessage()); assertNotNull(failed.getStack()); } @@ -170,15 +218,16 @@ void testDifferentStatuses() { @Test @DisplayName("Should handle example data for parameterized tests") void testExampleData() { + Object exampleData = Arrays.asList("param1", "param2", "param3"); TestResult result = TestResult.builder() - .withTitle("Parameterized Test") - .withSuiteTitle("Suite") - .withFile("Test.java") - .withStatus("passed") - .withExample(exampleData) - .build(); + .withTitle("Parameterized Test") + .withSuiteTitle("Suite") + .withFile("Test.java") + .withStatus("passed") + .withExample(exampleData) + .build(); assertNotNull(result.getExample()); assertEquals(exampleData, result.getExample()); @@ -187,6 +236,7 @@ void testExampleData() { @Test @DisplayName("Should handle multiple test steps") void testMultipleSteps() { + TestStep step1 = new TestStep(); step1.setStepTitle("Step 1"); step1.setDuration(100); @@ -198,16 +248,18 @@ void testMultipleSteps() { List steps = Arrays.asList(step1, step2); TestResult result = TestResult.builder() - .withTitle("Test with Steps") - .withSuiteTitle("Suite") - .withFile("Test.java") - .withStatus("passed") - .withSteps(steps) - .build(); + .withTitle("Test with Steps") + .withSuiteTitle("Suite") + .withFile("Test.java") + .withStatus("passed") + .withSteps(steps) + .build(); assertEquals(2, result.getSteps().size()); + assertEquals("Step 1", result.getSteps().get(0).getStepTitle()); assertEquals("Step 2", result.getSteps().get(1).getStepTitle()); + assertEquals(100, result.getSteps().get(0).getDuration()); assertEquals(200, result.getSteps().get(1).getDuration()); } @@ -215,6 +267,7 @@ void testMultipleSteps() { @Test @DisplayName("Should create empty TestResult with default constructor") void testDefaultConstructor() { + TestResult result = new TestResult(); assertNull(result.getTitle()); @@ -227,5 +280,24 @@ void testDefaultConstructor() { assertNull(result.getExample()); assertNull(result.getRid()); assertNull(result.getSteps()); + assertNull(result.getLinks()); + assertNull(result.isOverwrite()); + } + + @Test + @DisplayName("Should handle empty collections") + void testEmptyCollections() { + + TestResult result = TestResult.builder() + .withTitle("Test") + .withSteps(Collections.emptyList()) + .withLinks(Collections.emptyList()) + .build(); + + assertNotNull(result.getSteps()); + assertTrue(result.getSteps().isEmpty()); + + assertNotNull(result.getLinks()); + assertTrue(result.getLinks().isEmpty()); } -} +} \ No newline at end of file diff --git a/java-reporter-core/src/test/java/io/testomat/core/step/StepAspectTest.java b/java-reporter-core/src/test/java/io/testomat/core/step/StepAspectTest.java index 25a40792..16b6171b 100644 --- a/java-reporter-core/src/test/java/io/testomat/core/step/StepAspectTest.java +++ b/java-reporter-core/src/test/java/io/testomat/core/step/StepAspectTest.java @@ -287,6 +287,71 @@ void testLifecycleCleanup(){ assertNull(StepLifecycle.current()); } + @Test + @DisplayName("Should set passed status for successful step") + void testPassedStatus() { + simpleStep(); + TestStep step = StepStorage.getSteps().get(0); + assertEquals(StepStatus.passed, step.getStatus()); + } + + @Test + @DisplayName("Should store stack trace log for failed step") + void testFailureLogStored() { + assertThrows(RuntimeException.class, this::stepThatThrows); + TestStep step = StepStorage.getSteps().get(0); + assertNotNull(step.getLog()); + assertTrue(step.getLog().contains("stepThatThrows")); + } + + @Test + @DisplayName("Should set user category by default") + void testDefaultCategory() { + simpleStep(); + TestStep step = StepStorage.getSteps().get(0); + assertEquals("user", step.getCategory()); + } + + @Test + @DisplayName("Should store duration for failed step") + void testFailedStepDuration() { + assertThrows(RuntimeException.class, this::stepThatThrows); + TestStep step = StepStorage.getSteps().get(0); + assertTrue(step.getDuration() >= 0); + } + + @Test + @DisplayName("Should keep unresolved placeholders") + void testUnresolvedPlaceholder() { + unresolvedPlaceholderStep("John"); + TestStep step = StepStorage.getSteps().get(0); + assertEquals("User John {1}", step.getStepTitle()); + } + + @Test + @DisplayName("Should support repeated placeholders") + void testRepeatedPlaceholders() { + repeatedPlaceholderStep("Bob"); + TestStep step = StepStorage.getSteps().get(0); + assertEquals("User Bob again Bob", step.getStepTitle()); + } + + @Test + @DisplayName("Should handle empty string parameter") + void testEmptyStringParameter() { + stepWithNullableParameter(""); + TestStep step = StepStorage.getSteps().get(0); + assertEquals("Process value: ", step.getStepTitle()); + } + + @Step("User {0} {1}") + private void unresolvedPlaceholderStep(String name) { + } + + @Step("User {0} again {0}") + private void repeatedPlaceholderStep(String value) { + } + @Step("no artifacts") private void stepWithoutArtifacts(){} diff --git a/java-reporter-cucumber/pom.xml b/java-reporter-cucumber/pom.xml index 7cba4e58..ceb2e445 100644 --- a/java-reporter-cucumber/pom.xml +++ b/java-reporter-cucumber/pom.xml @@ -6,7 +6,7 @@ io.testomat java-reporter-cucumber - 0.7.13 + 0.7.14 jar Testomat.io Java Reporter Cucumber @@ -51,7 +51,7 @@ io.testomat java-reporter-core - 0.11.6 + 0.12.0 org.slf4j diff --git a/java-reporter-junit/pom.xml b/java-reporter-junit/pom.xml index e0261962..42864c07 100644 --- a/java-reporter-junit/pom.xml +++ b/java-reporter-junit/pom.xml @@ -6,7 +6,7 @@ io.testomat java-reporter-junit - 0.8.5 + 0.8.6 jar Testomat.io Java Reporter JUnit @@ -51,7 +51,7 @@ io.testomat java-reporter-core - 0.11.6 + 0.12.0 org.slf4j diff --git a/java-reporter-junit/src/main/java/io/testomat/junit/constructor/JUnitTestResultConstructor.java b/java-reporter-junit/src/main/java/io/testomat/junit/constructor/JUnitTestResultConstructor.java index a07cc186..8e342301 100644 --- a/java-reporter-junit/src/main/java/io/testomat/junit/constructor/JUnitTestResultConstructor.java +++ b/java-reporter-junit/src/main/java/io/testomat/junit/constructor/JUnitTestResultConstructor.java @@ -130,7 +130,8 @@ private TestResult createTestResult(TestMetadata metadata, .withFile(metadata.getFile()) .withMessage(message) .withStatus(status) - .withStack(stack); + .withStack(stack) + .withLinks(metadata.getLinks());; if (example != null) { builder.withExample(example); diff --git a/java-reporter-junit/src/main/java/io/testomat/junit/extractor/JunitMetaDataExtractor.java b/java-reporter-junit/src/main/java/io/testomat/junit/extractor/JunitMetaDataExtractor.java index aab2be99..f22242d2 100644 --- a/java-reporter-junit/src/main/java/io/testomat/junit/extractor/JunitMetaDataExtractor.java +++ b/java-reporter-junit/src/main/java/io/testomat/junit/extractor/JunitMetaDataExtractor.java @@ -1,12 +1,18 @@ package io.testomat.junit.extractor; +import io.testomat.core.annotation.LinkTest; import io.testomat.core.annotation.TestId; import io.testomat.core.annotation.Title; import io.testomat.core.exception.NoMethodInContextException; +import io.testomat.core.model.Link; import io.testomat.core.model.TestMetadata; import io.testomat.junit.extractor.strategy.ParameterExtractorService; import io.testomat.junit.util.ParameterizedTestSupport; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import org.junit.jupiter.api.extension.ExtensionContext; /** @@ -46,7 +52,8 @@ public TestMetadata extractTestMetadata(ExtensionContext context) { buildTestTitle(method, context), extractTestId(method), testClass.getSimpleName(), - buildFilePath(testClass) + buildFilePath(testClass), + getLinks(method) ); } @@ -134,6 +141,21 @@ public static String extractTestId(Method method) { return annotation != null ? annotation.value() : null; } + /** + * Gets test ids from @LinkTest annotation. + */ + private List getLinks(Method method) { + LinkTest annotation = method.getAnnotation(LinkTest.class); + + if (annotation == null || annotation.value().length == 0) { + return Collections.emptyList(); + } + + return Arrays.stream(annotation.value()) + .map(Link::test) + .collect(Collectors.toList()); + } + private String buildFilePath(Class testClass) { String packagePath = testClass.getPackage().getName().replace('.', '/'); return packagePath + "/" + testClass.getSimpleName() + ".java"; diff --git a/java-reporter-junit/src/test/java/io/testomat/junit/constructor/JUnitTestResultConstructorTest.java b/java-reporter-junit/src/test/java/io/testomat/junit/constructor/JUnitTestResultConstructorTest.java index 910604c6..e0c7d4f4 100644 --- a/java-reporter-junit/src/test/java/io/testomat/junit/constructor/JUnitTestResultConstructorTest.java +++ b/java-reporter-junit/src/test/java/io/testomat/junit/constructor/JUnitTestResultConstructorTest.java @@ -2,15 +2,16 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.testomat.core.model.Link; import io.testomat.core.model.TestMetadata; import io.testomat.core.model.TestResult; import io.testomat.junit.extractor.JunitMetaDataExtractor; -import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -333,4 +334,60 @@ private void setupBasicMetadata() { when(mockMetadata.getTitle()).thenReturn("TestMethod"); when(mockMetadata.getFile()).thenReturn("TestClass.java"); } + + @Test + @DisplayName("Should copy links from metadata to result") + void shouldCopyLinksFromMetadataToResult() { + List links = Arrays.asList( + Link.test("T-123"), + Link.label("Smoke") + ); + + when(mockMetadata.getSuiteTitle()).thenReturn("Suite"); + when(mockMetadata.getTestId()).thenReturn("test-1"); + when(mockMetadata.getTitle()).thenReturn("Test"); + when(mockMetadata.getFile()).thenReturn("Test.java"); + when(mockMetadata.getLinks()).thenReturn(links); + + when(mockExtractor.isParameterizedTest(mockContext)).thenReturn(false); + when(mockContext.getExecutionException()).thenReturn(Optional.empty()); + + TestResult result = constructor.constructTestRunResult( + mockMetadata, + "message", + "passed", + mockContext + ); + + assertThat(result.getLinks()).isNotNull(); + assertThat(result.getLinks()).hasSize(2); + + assertThat(result.getLinks().get(0).getTest()) + .isEqualTo("T-123"); + + assertThat(result.getLinks().get(1).getLabel()) + .isEqualTo("Smoke"); + } + + @Test + @DisplayName("Should handle null links") + void shouldHandleNullLinks() { + when(mockMetadata.getSuiteTitle()).thenReturn("Suite"); + when(mockMetadata.getTestId()).thenReturn("test-1"); + when(mockMetadata.getTitle()).thenReturn("Test"); + when(mockMetadata.getFile()).thenReturn("Test.java"); + when(mockMetadata.getLinks()).thenReturn(null); + + when(mockExtractor.isParameterizedTest(mockContext)).thenReturn(false); + when(mockContext.getExecutionException()).thenReturn(Optional.empty()); + + TestResult result = constructor.constructTestRunResult( + mockMetadata, + "message", + "passed", + mockContext + ); + + assertThat(result.getLinks()).isNull(); + } } \ No newline at end of file diff --git a/java-reporter-junit/src/test/java/io/testomat/junit/extractor/JunitMetaDataExtractorTest.java b/java-reporter-junit/src/test/java/io/testomat/junit/extractor/JunitMetaDataExtractorTest.java index 1c93445a..1cbdc1a5 100644 --- a/java-reporter-junit/src/test/java/io/testomat/junit/extractor/JunitMetaDataExtractorTest.java +++ b/java-reporter-junit/src/test/java/io/testomat/junit/extractor/JunitMetaDataExtractorTest.java @@ -12,6 +12,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import io.testomat.core.annotation.LinkTest; import io.testomat.core.annotation.TestId; import io.testomat.core.annotation.Title; import io.testomat.core.exception.NoMethodInContextException; @@ -377,6 +378,45 @@ void shouldHandleCaseWhenAnnotationsReturnNullValues() throws NoSuchMethodExcept } } + @Test + @DisplayName("Should extract links from LinkTest annotation") + void shouldExtractLinks() throws Exception { + Method testMethod = TestMethodHolder.class.getMethod("methodWithLinks"); + + when(mockExtensionContext.getTestMethod()) + .thenReturn(Optional.of(testMethod)); + + doReturn(TestMethodHolder.class) + .when(mockExtensionContext) + .getRequiredTestClass(); + + TestMetadata result = extractor.extractTestMetadata(mockExtensionContext); + + assertNotNull(result.getLinks()); + assertEquals(2, result.getLinks().size()); + + assertEquals("T-1", result.getLinks().get(0).getTest()); + assertEquals("T-2", result.getLinks().get(1).getTest()); + } + + @Test + @DisplayName("Should return empty links when LinkTest annotation absent") + void shouldReturnEmptyLinksWhenNoAnnotation() throws Exception { + Method testMethod = TestMethodHolder.class.getMethod("plainTestMethod"); + + when(mockExtensionContext.getTestMethod()) + .thenReturn(Optional.of(testMethod)); + + doReturn(TestMethodHolder.class) + .when(mockExtensionContext) + .getRequiredTestClass(); + + TestMetadata result = extractor.extractTestMetadata(mockExtensionContext); + + assertNotNull(result.getLinks()); + assertTrue(result.getLinks().isEmpty()); + } + // Test helper classes - separate from nested test classes to avoid Java 11 compatibility issues public static class TestMethodHolder { @Title("Custom Title") @@ -404,6 +444,10 @@ public void emptyAnnotationsMethod() { @TestId(" ") public void whitespaceAnnotationsMethod() { } + + @LinkTest({"T-1", "T-2"}) + public void methodWithLinks() { + } } public static class AnotherTestClass { diff --git a/java-reporter-junit/src/test/java/io/testomat/junit/reporter/JunitTestReporterTest.java b/java-reporter-junit/src/test/java/io/testomat/junit/reporter/JunitTestReporterTest.java index c6af33bd..6b8c3aec 100644 --- a/java-reporter-junit/src/test/java/io/testomat/junit/reporter/JunitTestReporterTest.java +++ b/java-reporter-junit/src/test/java/io/testomat/junit/reporter/JunitTestReporterTest.java @@ -79,7 +79,7 @@ void reportTestResult_WhenRunManagerActive_ShouldReportTestSuccessfully() { String message = "Test passed successfully"; String uniqueId = "test-unique-id-123"; - TestMetadata metadata = new TestMetadata("Test Title", "test-id", "TestSuite", "test/file.java"); + TestMetadata metadata = new TestMetadata("Test Title", "test-id", "TestSuite", "test/file.java", null); TestResult expectedResult = TestResult.builder() .withTitle("Test Title") .withStatus(status) @@ -105,7 +105,7 @@ void reportTestResult_WhenRunManagerActive_ShouldReportTestSuccessfully() { @Test void reportTestResult_WhenResultConstructionFails_ShouldThrowException() { // Given - TestMetadata metadata = new TestMetadata("Test Title", "test-id", "TestSuite", "test/file.java"); + TestMetadata metadata = new TestMetadata("Test Title", "test-id", "TestSuite", "test/file.java", null); when(runManager.isActive()).thenReturn(true); when(context.getUniqueId()).thenReturn("test-id"); @@ -126,7 +126,7 @@ void reportTestResult_WhenResultConstructionFails_ShouldThrowException() { @Test void reportTestResult_WhenReportingFails_ShouldThrowException() { // Given - TestMetadata metadata = new TestMetadata("Test Title", "test-id", "TestSuite", "test/file.java"); + TestMetadata metadata = new TestMetadata("Test Title", "test-id", "TestSuite", "test/file.java", null); TestResult result = TestResult.builder().withTitle("Test Title").build(); when(runManager.isActive()).thenReturn(true); @@ -153,7 +153,7 @@ void reportTestResult_ConcurrentExecution_ShouldHandleThreadSafety() throws Inte ExecutorService executor = Executors.newFixedThreadPool(numberOfThreads); AtomicInteger successCount = new AtomicInteger(0); - TestMetadata metadata = new TestMetadata("Test Title", "test-id", "TestSuite", "test/file.java"); + TestMetadata metadata = new TestMetadata("Test Title", "test-id", "TestSuite", "test/file.java", null); TestResult result = TestResult.builder().withTitle("Test Title").build(); when(runManager.isActive()).thenReturn(true); @@ -191,8 +191,8 @@ void reportTestResult_MultipleDifferentTests_ShouldUseDifferentLocks() throws In ExtensionContext context1 = mock(ExtensionContext.class); ExtensionContext context2 = mock(ExtensionContext.class); - TestMetadata metadata1 = new TestMetadata("Test 1", "id-1", "Suite1", "file1.java"); - TestMetadata metadata2 = new TestMetadata("Test 2", "id-2", "Suite2", "file2.java"); + TestMetadata metadata1 = new TestMetadata("Test 1", "id-1", "Suite1", "file1.java", null); + TestMetadata metadata2 = new TestMetadata("Test 2", "id-2", "Suite2", "file2.java", null); TestResult result1 = TestResult.builder().withTitle("Test 1").build(); TestResult result2 = TestResult.builder().withTitle("Test 2").build(); @@ -251,7 +251,7 @@ void reportTestResult_VerifyCorrectParametersPassedToConstructor() { // Given String status = "failed"; String message = "Custom failure message"; - TestMetadata metadata = new TestMetadata("Test Title", "test-id", "TestSuite", "test/file.java"); + TestMetadata metadata = new TestMetadata("Test Title", "test-id", "TestSuite", "test/file.java", null); TestResult result = TestResult.builder().withTitle("Test Title").build(); when(runManager.isActive()).thenReturn(true); diff --git a/java-reporter-karate/pom.xml b/java-reporter-karate/pom.xml index e4532e90..47d9f141 100644 --- a/java-reporter-karate/pom.xml +++ b/java-reporter-karate/pom.xml @@ -6,7 +6,7 @@ io.testomat java-reporter-karate - 0.2.7 + 0.2.8 jar Testomat.io Java Reporter Karate @@ -52,7 +52,7 @@ io.testomat java-reporter-core - 0.11.6 + 0.12.0 io.karatelabs diff --git a/java-reporter-testng/pom.xml b/java-reporter-testng/pom.xml index de70013b..d5eab291 100644 --- a/java-reporter-testng/pom.xml +++ b/java-reporter-testng/pom.xml @@ -6,7 +6,7 @@ io.testomat java-reporter-testng - 0.7.12 + 0.7.13 jar Testomat.io Java Reporter TestNG @@ -47,7 +47,7 @@ io.testomat java-reporter-core - 0.11.6 + 0.12.0 org.slf4j diff --git a/java-reporter-testng/src/main/java/io/testomat/testng/constructor/TestNgTestResultConstructor.java b/java-reporter-testng/src/main/java/io/testomat/testng/constructor/TestNgTestResultConstructor.java index f6e9f631..683b50e6 100644 --- a/java-reporter-testng/src/main/java/io/testomat/testng/constructor/TestNgTestResultConstructor.java +++ b/java-reporter-testng/src/main/java/io/testomat/testng/constructor/TestNgTestResultConstructor.java @@ -73,7 +73,8 @@ private TestResult buildTestResult(TestResultWrapper wrapper, String message, St .withMessage(message) .withStack(stack) .withExample(wrapper.getExample()) - .withRid(wrapper.getRid()); + .withRid(wrapper.getRid()) + .withLinks(metadata.getLinks()); if (!steps.isEmpty()) { builder.withSteps(steps); diff --git a/java-reporter-testng/src/main/java/io/testomat/testng/extractor/TestNgMetaDataExtractor.java b/java-reporter-testng/src/main/java/io/testomat/testng/extractor/TestNgMetaDataExtractor.java index afce8a48..115b4656 100644 --- a/java-reporter-testng/src/main/java/io/testomat/testng/extractor/TestNgMetaDataExtractor.java +++ b/java-reporter-testng/src/main/java/io/testomat/testng/extractor/TestNgMetaDataExtractor.java @@ -1,9 +1,15 @@ package io.testomat.testng.extractor; +import io.testomat.core.annotation.LinkTest; import io.testomat.core.annotation.TestId; import io.testomat.core.annotation.Title; +import io.testomat.core.model.Link; import io.testomat.core.model.TestMetadata; import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import org.testng.ITestResult; /** @@ -36,8 +42,9 @@ public TestMetadata extractTestMetadata(TestNgTestWrapper wrapper) { String title = getTestTitle(method); String testId = getTestId(method); String filePath = getFilePath(testClass); + List link = getLinks(method); - return new TestMetadata(title, testId, suiteTitle, filePath); + return new TestMetadata(title, testId, suiteTitle, filePath, link); } /** @@ -67,4 +74,19 @@ private String getTestTitle(Method method) { Title titleAnnotation = method.getAnnotation(Title.class); return titleAnnotation != null ? titleAnnotation.value() : method.getName(); } + + /** + * Gets test ids from @LinkTest annotation. + */ + private List getLinks(Method method) { + LinkTest annotation = method.getAnnotation(LinkTest.class); + + if (annotation == null || annotation.value().length == 0) { + return Collections.emptyList(); + } + + return Arrays.stream(annotation.value()) + .map(Link::test) + .collect(Collectors.toList()); + } } diff --git a/java-reporter-testng/src/test/java/io/reporter/testng/constructor/TestNgTestResultConstructorTest.java b/java-reporter-testng/src/test/java/io/reporter/testng/constructor/TestNgTestResultConstructorTest.java index bb494257..63e3a6a9 100644 --- a/java-reporter-testng/src/test/java/io/reporter/testng/constructor/TestNgTestResultConstructorTest.java +++ b/java-reporter-testng/src/test/java/io/reporter/testng/constructor/TestNgTestResultConstructorTest.java @@ -10,10 +10,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; +import io.testomat.core.model.Link; import io.testomat.core.model.TestMetadata; import io.testomat.core.model.TestResult; +import io.testomat.core.step.StepStorage; +import io.testomat.core.step.TestStep; import io.testomat.testng.constructor.TestNgTestResultConstructor; import io.testomat.testng.constructor.TestResultWrapper; +import java.util.Arrays; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -403,4 +408,80 @@ void shouldHandleEmptyMessage() { assertEquals("", result.getMessage()); assertNull(result.getStack()); } + + @Test + @DisplayName("Should copy links from metadata") + void shouldCopyLinksFromMetadata() { + List links = Arrays.asList( + Link.test("T-1"), + Link.label("Smoke") + ); + + when(mockMetadata.getLinks()).thenReturn(links); + when(mockWrapper.getMessage()).thenReturn("message"); + + TestResult result = constructor.constructTestRunResult(mockWrapper); + + assertNotNull(result.getLinks()); + assertEquals(2, result.getLinks().size()); + + assertEquals("T-1", result.getLinks().get(0).getTest()); + assertEquals("Smoke", result.getLinks().get(1).getLabel()); + } + + @Test + @DisplayName("Should copy example data") + void shouldCopyExampleData() { + Object[] example = {"user", 123}; + + when(mockWrapper.getExample()).thenReturn(example); + when(mockWrapper.getMessage()).thenReturn("message"); + + TestResult result = constructor.constructTestRunResult(mockWrapper); + + assertEquals(example, result.getExample()); + } + + @Test + @DisplayName("Should copy rid") + void shouldCopyRid() { + when(mockWrapper.getRid()).thenReturn("rid-123"); + when(mockWrapper.getMessage()).thenReturn("message"); + + TestResult result = constructor.constructTestRunResult(mockWrapper); + + assertEquals("rid-123", result.getRid()); + } + + @Test + @DisplayName("Should collect steps from StepStorage") + void shouldCollectSteps() { + TestStep step = new TestStep(); + step.setStepTitle("Open page"); + + StepStorage.addStep(step); + + when(mockWrapper.getMessage()).thenReturn("message"); + + TestResult result = constructor.constructTestRunResult(mockWrapper); + + assertNotNull(result.getSteps()); + assertEquals(1, result.getSteps().size()); + assertEquals("Open page", result.getSteps().get(0).getStepTitle()); + } + + @Test + @DisplayName("Should clear StepStorage after result construction") + void shouldClearStepStorage() { + TestStep step = new TestStep(); + step.setStepTitle("Step"); + + StepStorage.addStep(step); + + when(mockWrapper.getMessage()).thenReturn("message"); + + constructor.constructTestRunResult(mockWrapper); + + assertTrue(StepStorage.getSteps().isEmpty()); + } } \ No newline at end of file diff --git a/java-reporter-testng/src/test/java/io/testomat/testng/extractor/TestNgMetaDataExtractorTest.java b/java-reporter-testng/src/test/java/io/testomat/testng/extractor/TestNgMetaDataExtractorTest.java index ff26cb81..f185682e 100644 --- a/java-reporter-testng/src/test/java/io/testomat/testng/extractor/TestNgMetaDataExtractorTest.java +++ b/java-reporter-testng/src/test/java/io/testomat/testng/extractor/TestNgMetaDataExtractorTest.java @@ -5,7 +5,9 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.testng.AssertJUnit.assertTrue; +import io.testomat.core.annotation.LinkTest; import io.testomat.core.annotation.TestId; import io.testomat.core.annotation.Title; import io.testomat.core.model.TestMetadata; @@ -179,6 +181,46 @@ void shouldHandleClassWithoutExplicitPackage() throws Exception { assertEquals("java/lang/String.java", metadata.getFile()); } + @Test + @DisplayName("Should extract links from LinkTest annotation") + void shouldExtractLinksFromAnnotation() throws Exception { + Method method = TestClassForTesting.class + .getDeclaredMethod("testMethodWithLinks"); + + TestNgTestWrapper wrapper = + new TestNgTestWrapper(method, TestClassForTesting.class); + + TestMetadata metadata = extractor.extractTestMetadata(wrapper); + + assertNotNull(metadata.getLinks()); + assertEquals(2, metadata.getLinks().size()); + + assertEquals( + "T-1", + metadata.getLinks().get(0).getTest() + ); + + assertEquals( + "T-2", + metadata.getLinks().get(1).getTest() + ); + } + + @Test + @DisplayName("Should return empty links when LinkTest annotation absent") + void shouldReturnEmptyLinksWhenNoAnnotation() throws Exception { + Method method = TestClassForTesting.class + .getDeclaredMethod("testMethodWithoutAnnotations"); + + TestNgTestWrapper wrapper = + new TestNgTestWrapper(method, TestClassForTesting.class); + + TestMetadata metadata = extractor.extractTestMetadata(wrapper); + + assertNotNull(metadata.getLinks()); + assertTrue(metadata.getLinks().isEmpty()); + } + // Test class with annotations for testing static class TestClassForTesting { @@ -193,5 +235,9 @@ public void testMethodWithoutTitle() { public void testMethodWithoutAnnotations() { } + + @LinkTest({"T-1", "T-2"}) + public void testMethodWithLinks() { + } } } diff --git a/pom.xml b/pom.xml index 7eb97f0d..f6cc7c33 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.testomat java-reporter - 0.3.1 + 0.3.2 pom Testomat.io Java Reporter diff --git a/testomat-allure-adapter/pom.xml b/testomat-allure-adapter/pom.xml index 2434a2e9..0fd29201 100644 --- a/testomat-allure-adapter/pom.xml +++ b/testomat-allure-adapter/pom.xml @@ -6,7 +6,7 @@ io.testomat testomat-allure-adapter - 0.0.3 + 0.0.4 jar Testomat.io Testomat Allure adapter @@ -66,7 +66,7 @@ io.testomat java-reporter-core - 0.11.6 + 0.12.0 io.qameta.allure diff --git a/testomat-allure-adapter/src/main/java/io/testomat/resolver/TestNgResolver.java b/testomat-allure-adapter/src/main/java/io/testomat/resolver/TestNgResolver.java index 98593bd0..3091ea96 100644 --- a/testomat-allure-adapter/src/main/java/io/testomat/resolver/TestNgResolver.java +++ b/testomat-allure-adapter/src/main/java/io/testomat/resolver/TestNgResolver.java @@ -1,5 +1,6 @@ package io.testomat.resolver; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,7 +18,7 @@ public class TestNgResolver implements TestMetadataResolver { */ @Override public String resolve(Method method) { - for (var a : method.getAnnotations()) { + for (Annotation a : method.getAnnotations()) { if (a.annotationType().getName().equals("org.testng.annotations.Test")) { try { Method m = a.annotationType().getMethod("description"); From 773d3f3aa407a90151b58f59a406fc29ef8e5500 Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 10 May 2026 22:43:36 +0300 Subject: [PATCH 2/3] Issue-127 Change readme --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index e0714bea..4c6d6d46 100644 --- a/README.md +++ b/README.md @@ -226,6 +226,18 @@ public class LoginTests { } } ``` +## LinkTest + +Links test IDs to the current test in the report. This allows you to associate multiple test cases with the current test execution. + +```java + @TestId("aba4b142") + @LinkTest({"aba4b144", "aba4b144"}) + @Test + public void test() { + // Your test code here + } +``` ### 🥒 For Cucumber From 9bc963cda6d20648d44482e7fcab91ebdc4e2e89 Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 10 May 2026 23:16:16 +0300 Subject: [PATCH 3/3] Issue-127 Change readme --- README.md | 1 + .../io/testomat/core/constants/ArtifactPropertyNames.java | 2 ++ .../io/testomat/core/constants/PropertyNameConstants.java | 2 -- .../src/main/java/io/testomat/core/facade/Testomatio.java | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4c6d6d46..46e78267 100644 --- a/README.md +++ b/README.md @@ -343,6 +343,7 @@ Artifacts are stored in external S3 buckets. S3 Access can be configured in **tw |-------------------------------|--------------------------------------------------|-------------| | `testomatio.artifact.disable` | Completely disable artifact uploading | `false` | | `testomatio.artifact.private` | Keep artifacts private (no public URLs) | `false` | +| `testomatio.step.artifacts.enabled` | Enables uploading artifacts for test steps | `false` | | `s3.force-path-style` | Use path-style URLs for S3-compatible storage | `false` | | `s3.endpoint` | Custom endpoint to be used with force-path-style | `false` | | `s3.bucket` | Provides bucket name for configuration | | diff --git a/java-reporter-core/src/main/java/io/testomat/core/constants/ArtifactPropertyNames.java b/java-reporter-core/src/main/java/io/testomat/core/constants/ArtifactPropertyNames.java index 7e4e48bf..b6747c05 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/constants/ArtifactPropertyNames.java +++ b/java-reporter-core/src/main/java/io/testomat/core/constants/ArtifactPropertyNames.java @@ -12,4 +12,6 @@ public class ArtifactPropertyNames { public static final String PRIVATE_ARTIFACTS_PROPERTY_NAME = "testomatio.artifact.private"; public static final String ARTIFACT_DISABLE_PROPERTY_NAME = "testomatio.artifact.disable"; public static final String MAX_SIZE_ARTIFACTS_PROPERTY_NAME = "testomatio.artifact.max-size"; + + public static final String STEP_ARTIFACT_ENABLED_PROPERTY_NAME = "testomatio.step.artifacts.enabled"; } diff --git a/java-reporter-core/src/main/java/io/testomat/core/constants/PropertyNameConstants.java b/java-reporter-core/src/main/java/io/testomat/core/constants/PropertyNameConstants.java index 04833a9a..3d662ca1 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/constants/PropertyNameConstants.java +++ b/java-reporter-core/src/main/java/io/testomat/core/constants/PropertyNameConstants.java @@ -17,6 +17,4 @@ public class PropertyNameConstants { public static final String SHARED_RUN_PROPERTY_NAME = "testomatio.shared.run"; public static final String SHARED_TIMEOUT_PROPERTY_NAME = "testomatio.shared.run.timeout"; - - public static final String UPLOAD_STEP_ARTIFACTS = "testomatio.step.artifacts.enabled"; } diff --git a/java-reporter-core/src/main/java/io/testomat/core/facade/Testomatio.java b/java-reporter-core/src/main/java/io/testomat/core/facade/Testomatio.java index b316ac01..c970821a 100644 --- a/java-reporter-core/src/main/java/io/testomat/core/facade/Testomatio.java +++ b/java-reporter-core/src/main/java/io/testomat/core/facade/Testomatio.java @@ -1,6 +1,6 @@ package io.testomat.core.facade; -import static io.testomat.core.constants.PropertyNameConstants.UPLOAD_STEP_ARTIFACTS; +import static io.testomat.core.constants.ArtifactPropertyNames.STEP_ARTIFACT_ENABLED_PROPERTY_NAME; import io.testomat.core.facade.methods.artifact.manager.ArtifactManager; import io.testomat.core.facade.methods.label.LabelStorage; @@ -40,7 +40,7 @@ public static void artifact(String... directories) { * @param directories artifact directories to attach (ignored if null or empty) */ public static void stepArtifact(String... directories) { - if (!provider.getBooleanProperty(UPLOAD_STEP_ARTIFACTS)) { + if (!provider.getBooleanProperty(STEP_ARTIFACT_ENABLED_PROPERTY_NAME)) { return; } TestStep testStep = StepLifecycle.current();