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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -331,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 | |
Expand Down
2 changes: 1 addition & 1 deletion java-reporter-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<groupId>io.testomat</groupId>
<artifactId>java-reporter-core</artifactId>
<version>0.11.6</version>
<version>0.12.0</version>
<packaging>jar</packaging>

<name>Testomat.io Reporter Core</name>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -179,9 +188,10 @@ private Map<String, Object> 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<String, Object> storageEntry = new HashMap<>();
Expand Down Expand Up @@ -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).
*
* <p>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);
Expand Down Expand Up @@ -290,13 +362,30 @@ private void addMeta(Map<String, Object> body, String rid) {
}
}

private void addLinks(Map<String, Object> body, String rid) {
body.put("links", new ArrayList<>());
List<Object> links = (List<Object>) body.get("links");
private void addLinks(TestResult result, String rid) {
List<Map<String, String>> labels = LabelStorage.LINKED_LABEL_STORAGE.get(rid);
if (labels != null && !labels.isEmpty()) {
links.addAll(labels);
if (labels == null || labels.isEmpty()) {
return;
}

List<Link> links = new ArrayList<>(
Optional.ofNullable(result.getLinks())
.orElse(Collections.emptyList())
);

Set<String> 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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Original file line number Diff line number Diff line change
@@ -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";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package io.testomat.core.facade;

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;
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;
Expand All @@ -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.
*
Expand All @@ -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(STEP_ARTIFACT_ENABLED_PROPERTY_NAME)) {
return;
}
TestStep testStep = StepLifecycle.current();

if(directories == null || directories.length == 0){
if (directories == null || directories.length == 0){
return;
}

Expand Down
26 changes: 26 additions & 0 deletions java-reporter-core/src/main/java/io/testomat/core/model/Link.java
Original file line number Diff line number Diff line change
@@ -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; }
}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -10,6 +12,7 @@ public class TestMetadata {
private String testId;
private String suiteTitle;
private String file;
private List<Link> links;

/**
* Creates test metadata with identification information.
Expand All @@ -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<Link> links) {
this.title = title;
this.testId = testId;
this.suiteTitle = suiteTitle;
this.file = file;
this.links = links;
}

public String getTitle() {
Expand Down Expand Up @@ -58,4 +62,12 @@ public String getFile() {
public void setFile(String file) {
this.file = file;
}

public List<Link> getLinks() {
return links;
}

public void setLinks(List<Link> links) {
this.links = links;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ public class TestResult {
/** Test steps executed during the test */
private List<TestStep> steps;

private List<Link> links;

/** Test result is overwritten when the test is executed multiple times */
private Boolean overwrite;

Expand All @@ -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<TestStep> steps) {
List<TestStep> steps, List<Link> links) {
this.title = title;
this.testId = testId;
this.suiteTitle = suiteTitle;
Expand All @@ -70,6 +72,7 @@ public TestResult(String title, String testId,
this.rid = rid;
this.overwrite = overwrite;
this.steps = steps;
this.links = links;
}

/**
Expand All @@ -88,6 +91,7 @@ public static class Builder {
private String rid;
private Boolean overwrite;
private List<TestStep> steps;
private List<Link> links;

public Builder withTitle(String title) {
this.title = title;
Expand Down Expand Up @@ -144,8 +148,13 @@ public Builder withSteps(List<TestStep> steps) {
return this;
}

public Builder withLinks(List<Link> 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);
}
}

Expand Down Expand Up @@ -245,4 +254,12 @@ public Boolean isOverwrite() {
public void setSteps(List<TestStep> steps) {
this.steps = steps;
}

public List<Link> getLinks() {
return links;
}

public void setLinks(List<Link> links) {
this.links = links;
}
}
Loading
Loading