-
Notifications
You must be signed in to change notification settings - Fork 0
Artifact support implementation #79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 35 commits
bc70dc8
3a3ef1d
1943ff8
85688f9
e853eaf
e193c9a
1c513d6
a77e5e5
842c821
b470b67
4c153bf
d031136
9b72600
60d5c35
6125ccd
d76bae1
7370edc
2904eb7
6658a31
05d4cc6
24d8ae3
231f60f
9bfad79
260a1de
a97ef00
ab27c1f
3c7b456
585c29b
70ac825
6576b47
20a141d
5f66cbc
11f9293
64731f3
c7cc19f
87ea07c
63714dc
5b0ecbc
7f3cdcb
9c0eaeb
525516b
b6ba521
969fd95
2ed01e5
bd907f0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,7 +28,7 @@ and team collaboration features. | |
| **Advanced error reporting** | Detailed test failure/skip descriptions | ✅ | ✅ | ✅ | | ||
| **TestId import** | Import test IDs from testomat.io into the codebase | ✅ | ✅ | ✅ | | ||
| **Parametrized tests support** | Enhanced support for parameterized testing | ✅ | ✅ | ✅ | | ||
| **Test artifacts support** | Screenshots, logs, and file attachments | ⏳ | ⏳ | ⏳ | | ||
| **Test artifacts support** | Screenshots, logs, and file attachments | ✅ | ✅ | ✅ | | ||
| **Step-by-step reporting** | Detailed test step execution tracking | ⏳ | ⏳ | ⏳ | | ||
| **Other frameworks support** | Karate, Gauge, etc. (Priority may change) | | | | | ||
|
||
|
@@ -104,7 +104,9 @@ Create the `cucumber.properties` file if you don't have one yet and add this lin | |
```properties | ||
cucumber.plugin=io.testomat.cucumber.listener.CucumberListener | ||
``` | ||
|
||
--- | ||
|
||
### 🔧 Advanced Custom Setup | ||
|
||
> **⚠️ Only use this if you need custom behavior** - like adding extra logic to test lifecycle events. | ||
|
@@ -283,7 +285,77 @@ Use these oneliners to **download jar and update** ids in one move | |
|
||
--- | ||
|
||
## 💡 Usage Examples | ||
## 📎 Test Artifacts Support | ||
|
||
The Java Reporter supports attaching files (screenshots, logs, videos, etc.) to your test results and uploading them to | ||
S3-compatible storage. | ||
Artifacts handling is enabled by default, but it won't affect the run if there are no artifacts provided (see options below). | ||
> NOTE: Current version handles artifacts synchronously. | ||
|
||
### Configuration | ||
|
||
Artifact handling can be configured in **two different ways**: | ||
YevheniiVlasenko marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
1. Make configurations on the [Testomat.io](https://app.testomat.io): | ||
Choose your project -> click **Settings** button on the left panel -> click **Artifacts** -> Toggle "**Share | ||
credentials**..." | ||
<img src=img/artifactsOnServerTurnOn.png alt="artifact example" width=50% /> | ||
|
||
2. Provide options as environment variables/jvm property/testomatio.properties file. | ||
|
||
> NOTE: Environment variables(env/jvm/testomatio.properties) take precedence over server-provided credentials. | ||
|
||
| Setting | Description | Default | | ||
|-------------------------------|--------------------------------------------------|-------------| | ||
| `testomatio.artifact.disable` | Completely disable artifact uploading | `false` | | ||
| `testomatio.artifact.private` | Keep artifacts private (no public URLs) | `false` | | ||
| `s3.force-path-style` | Use path-style URLs for S3-compatible storage | `false` | | ||
| `s3.endpoint` | Custom endpoint ot be used with force-path-style | `false` | | ||
| `s3.bucket` | Provides bucket name for configuration | | | ||
| `s3.access-key-id` | Access key for the bucket | | | ||
| `s3.region` | Bucket region | `us-west-1` | | ||
|
||
**Note**: S3 credentials can be configured either in properties file or provided automatically on Testomat.io UI. | ||
Environment variables take precedence over server-provided credentials. | ||
|
||
### Usage | ||
|
||
Use the `Testomatio` facade to attach files to your tests: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What other methods Testomatio contains? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None yet |
||
|
||
```java | ||
import io.testomat.core.facade.Testomatio; | ||
|
||
public class MyTest { | ||
|
||
@Test | ||
public void testWithScreenshot() { | ||
// Your test logic | ||
|
||
// Attach artifacts (screenshots, logs, etc.) | ||
Testomatio.artifact( | ||
"/path/to/screenshot.png", | ||
"/path/to/test.log" | ||
); | ||
} | ||
} | ||
``` | ||
Multiple directories can be provided to the `Testomatio.artifact(String ...)` facade method. | ||
YevheniiVlasenko marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
Please, make sure you provide path to artifact file including its extension. | ||
|
||
### How It Works | ||
|
||
1. **File Validation**: Only existing files with valid paths are processed | ||
|
||
2. **S3 Upload**: Files are uploaded to your S3 bucket with organized folder structure | ||
3. **Link Generation**: Public URLs are generated and attached to test results | ||
4. **Thread Safety**: Multiple concurrent tests can safely attach artifacts | ||
|
||
|
||
As the result you will see something like this on UI after run completed: | ||
<img src=img/artifactExample.png alt="artifact example" width=50% /> | ||
|
||
--- | ||
|
||
## 💡 Library Usage Examples | ||
|
||
### Basic Usage | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package io.testomat.core.artifact; | ||
|
||
import java.util.List; | ||
|
||
/** | ||
* Data class representing the relationship between test execution and its associated artifact links. | ||
* Contains test metadata and S3 URLs for uploaded artifacts. | ||
*/ | ||
public class ArtifactLinkData { | ||
private String rid; | ||
private final String testId; | ||
private final String testName; | ||
|
||
private final List<String> links; | ||
|
||
/** | ||
* Creates artifact link data for a test execution. | ||
* | ||
* @param testName name of the test | ||
* @param rid request identifier | ||
* @param testId unique test identifier | ||
* @param links list of S3 URLs for uploaded artifacts | ||
*/ | ||
public ArtifactLinkData(String testName, String rid, String testId, List<String> links) { | ||
this.testName = testName; | ||
this.rid = rid; | ||
this.testId = testId; | ||
this.links = links; | ||
} | ||
|
||
/** | ||
* Returns the list of artifact URLs. | ||
* | ||
* @return list of S3 URLs for artifacts | ||
*/ | ||
public List<String> getLinks() { | ||
return links; | ||
} | ||
|
||
/** | ||
* Returns the unique test identifier. | ||
* | ||
* @return test ID | ||
*/ | ||
public String getTestId() { | ||
return testId; | ||
} | ||
|
||
/** | ||
* Returns the request identifier. | ||
* | ||
* @return request ID | ||
*/ | ||
public String getRid() { | ||
return rid; | ||
} | ||
|
||
/** | ||
* Sets the request identifier. | ||
* | ||
* @param rid request ID to set | ||
*/ | ||
public void setRid(String rid) { | ||
this.rid = rid; | ||
} | ||
|
||
/** | ||
* Returns the test name. | ||
* | ||
* @return name of the test | ||
*/ | ||
public String getTestName() { | ||
return testName; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package io.testomat.core.artifact; | ||
|
||
import java.util.List; | ||
import java.util.concurrent.CopyOnWriteArrayList; | ||
|
||
/** | ||
* Thread-safe storage for artifact link data collected during test execution. | ||
* Maintains a collection of artifact links that will be associated with test results. | ||
*/ | ||
public class ArtifactLinkDataStorage { | ||
public static final List<ArtifactLinkData> ARTEFACT_LINK_DATA_STORAGE = | ||
new CopyOnWriteArrayList<>(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package io.testomat.core.artifact; | ||
|
||
/** | ||
* Builder for creating JSON request bodies containing artifact links for upload to the server. | ||
* Handles serialization of artifact data and test run information. | ||
*/ | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.fasterxml.jackson.databind.node.ArrayNode; | ||
import com.fasterxml.jackson.databind.node.ObjectNode; | ||
import java.util.List; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
public class LinkUploadBodyBuilder { | ||
private static final Logger log = LoggerFactory.getLogger(LinkUploadBodyBuilder.class); | ||
|
||
public String buildLinkUploadRequestBody(List<ArtifactLinkData> storedLinkData, String apiKey) { | ||
ObjectMapper mapper = new ObjectMapper(); | ||
ObjectNode rootNode = mapper.createObjectNode(); | ||
ArrayNode testsArray = mapper.createArrayNode(); | ||
|
||
for (ArtifactLinkData data : storedLinkData) { | ||
ObjectNode testNode = mapper.createObjectNode(); | ||
testNode.put("rid", data.getRid()); | ||
testNode.put("test_id", data.getTestId()); | ||
testNode.put("title", data.getTestName()); | ||
testNode.put("overwrite", "true"); | ||
testNode.set("artifacts", mapper.valueToTree(data.getLinks())); | ||
testsArray.add(testNode); | ||
} | ||
|
||
rootNode.put("api_key", apiKey); | ||
rootNode.set("tests", testsArray); | ||
|
||
String json = null; | ||
try { | ||
json = mapper.writeValueAsString(rootNode); | ||
} catch (JsonProcessingException e) { | ||
log.warn("Failed to convert convert link storage to json body"); | ||
} | ||
return json; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package io.testomat.core.artifact; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.CopyOnWriteArrayList; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* Thread-safe storage for reported test data with artifact linking capabilities. | ||
* Maintains test execution results and allows linking artifacts to specific tests by RID. | ||
*/ | ||
public class ReportedTestStorage { | ||
private static final Logger log = LoggerFactory.getLogger(ReportedTestStorage.class); | ||
private static final List<Map<String, Object>> STORAGE = new CopyOnWriteArrayList<>(); | ||
|
||
/** | ||
* Stores test execution data. | ||
* | ||
* @param body test data map containing test results and metadata | ||
*/ | ||
public static void store(Map<String, Object> body) { | ||
STORAGE.add(body); | ||
log.debug("Stored body: {}", body); | ||
} | ||
|
||
/** | ||
* Returns all stored test data. | ||
* | ||
* @return list of test data maps | ||
*/ | ||
public static List<Map<String, Object>> getStorage() { | ||
return STORAGE; | ||
} | ||
|
||
/** | ||
* Links artifacts to their corresponding tests using RID matching. | ||
* | ||
* @param artifactLinkData list of artifact link data to associate with tests | ||
*/ | ||
public static void linkArtifactsToTests(List<ArtifactLinkData> artifactLinkData) { | ||
for (ArtifactLinkData data : artifactLinkData) { | ||
STORAGE.stream() | ||
.filter(body -> data.getRid().equals(body.get("rid"))) | ||
.forEach(body -> body.put("artifacts", data.getLinks())); | ||
} | ||
for (ArtifactLinkData data : artifactLinkData) { | ||
log.debug("Linked: testId - {}, testName - {}, rid - {}, links - {}", | ||
data.getTestId(), data.getTestName(), data.getRid(), data.getLinks().get(0)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package io.testomat.core.artifact; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
/** | ||
* Thread-local storage for temporarily holding artifact file paths during test execution. | ||
* Ensures thread safety when multiple tests run concurrently. | ||
*/ | ||
public class TempArtifactDirectoriesStorage { | ||
public static final ThreadLocal<List<String>> DIRECTORIES = ThreadLocal.withInitial(ArrayList::new); | ||
|
||
public static void store(String dir) { | ||
DIRECTORIES.get().add(dir); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package io.testomat.core.artifact.client; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import software.amazon.awssdk.services.s3.S3Client; | ||
|
||
/** | ||
* AWS S3 client wrapper with built-in configuration management. | ||
* Provides singleton S3Client instances with custom endpoint and credential support. | ||
*/ | ||
public class AwsClient { | ||
private static final Logger log = LoggerFactory.getLogger(AwsClient.class); | ||
private volatile S3Client s3Client; | ||
private final S3ClientFactory clientFactory; | ||
|
||
public AwsClient() { | ||
this.clientFactory = new S3ClientFactory(); | ||
} | ||
|
||
/** | ||
* Test Constructor | ||
*/ | ||
public AwsClient(S3ClientFactory s3ClientFactory) { | ||
this.clientFactory = s3ClientFactory; | ||
} | ||
|
||
/** | ||
* Returns a configured S3Client instance using lazy initialization. | ||
* | ||
* @return configured S3Client instance | ||
*/ | ||
public S3Client getS3Client() { | ||
if (s3Client == null) { | ||
s3Client = clientFactory.createS3Client(); | ||
return s3Client; | ||
} | ||
return s3Client; | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.