Skip to content
Open
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
21 changes: 21 additions & 0 deletions allure-java-commons/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@ Allure.step("Create order", () -> {

High-level attachment APIs create attachment steps so attachments stay ordered with surrounding steps in Allure Report.

### Preconditions and Expected Results

Report preconditions and expected results separately from the test description:

```java
Allure.precondition("User is **logged in**");
Allure.expectedResult("**Dashboard** is displayed");

// Use explicit HTML for rich formatting.
Allure.preconditionHtml("<p>User is logged in</p>");
Allure.expectedResultHtml("<p>Dashboard is displayed</p>");
```

These helpers follow description metadata targeting: calls in a test update that test; calls in a before fixture
provide scope defaults for its linked tests. Each field inherits independently, and explicit test values,
including empty strings, take precedence over scope defaults. Calls outside a running executable or in an
after fixture have no effect.

Markdown and HTML values are stored independently. When both are provided, HTML takes precedence in reports
that support these fields, including Allure Report 3.

## Annotation API

The module provides annotations such as `@Step`, `@Attachment`, `@Owner`, `@Epic`, `@Feature`, and `@Story`.
Expand Down
48 changes: 48 additions & 0 deletions allure-java-commons/src/main/java/io/qameta/allure/Allure.java
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,54 @@ public static void descriptionHtml(final String descriptionHtml) {
getLifecycle().updateTestMetadata(metadata -> metadata.setDescriptionHtml(descriptionHtml));
}

/**
* Adds precondition to the current test, or to the scope of a running before fixture.
* Takes no effect if no executable is running or an after fixture is running.
* Expects Markdown content.
*
* @param precondition the precondition in Markdown format
* @see #preconditionHtml(String)
*/
public static void precondition(final String precondition) {
getLifecycle().updateTestMetadata(metadata -> metadata.setPrecondition(precondition));
}

/**
* Adds HTML precondition to the current test, or to the scope of a running before fixture.
* Takes no effect if no executable is running or an after fixture is running.
* The HTML value takes precedence over Markdown when rendered in the report.
*
* @param preconditionHtml the HTML precondition in HTML format
* @see #precondition(String)
*/
public static void preconditionHtml(final String preconditionHtml) {
getLifecycle().updateTestMetadata(metadata -> metadata.setPreconditionHtml(preconditionHtml));
}

/**
* Adds expected result to the current test, or to the scope of a running before fixture.
* Takes no effect if no executable is running or an after fixture is running.
* Expects Markdown content.
*
* @param expectedResult the expected result in Markdown format
* @see #expectedResultHtml(String)
*/
public static void expectedResult(final String expectedResult) {
getLifecycle().updateTestMetadata(metadata -> metadata.setExpectedResult(expectedResult));
}

/**
* Adds HTML expected result to the current test, or to the scope of a running before fixture.
* Takes no effect if no executable is running or an after fixture is running.
* The HTML value takes precedence over Markdown when rendered in the report.
*
* @param expectedResultHtml the HTML expected result in HTML format
* @see #expectedResult(String)
*/
public static void expectedResultHtml(final String expectedResultHtml) {
getLifecycle().updateTestMetadata(metadata -> metadata.setExpectedResultHtml(expectedResultHtml));
}

/**
* Adds an error from the given throwable that belongs to the test run rather than to a test or fixture.
* The error is passed synchronously to the configured results writer before this method returns.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1438,6 +1438,18 @@ private static void mergeScopeMetadata(final ScopeResult scope, final TestResult
if (Objects.isNull(testResult.getDescriptionHtml())) {
testResult.setDescriptionHtml(scope.getDescriptionHtml());
}
if (Objects.isNull(testResult.getPrecondition())) {
testResult.setPrecondition(scope.getPrecondition());
}
if (Objects.isNull(testResult.getPreconditionHtml())) {
testResult.setPreconditionHtml(scope.getPreconditionHtml());
}
if (Objects.isNull(testResult.getExpectedResult())) {
testResult.setExpectedResult(scope.getExpectedResult());
}
if (Objects.isNull(testResult.getExpectedResultHtml())) {
testResult.setExpectedResultHtml(scope.getExpectedResultHtml());
}
}

private static TestResultContainer toScopeContainer(final ScopeResult scope) {
Expand Down
144 changes: 144 additions & 0 deletions allure-java-commons/src/test/java/io/qameta/allure/AllureTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,150 @@ void shouldAddDescriptionHtml() {
.containsExactly(descriptionHtml);
}

/**
* Tests can report precondition independently from their description.
*/
@Description
@Test
void shouldAddPrecondition() {
final String value = "User is **logged in**";

final AllureResults results = runWithinTestContext(() -> Allure.precondition(value));

assertThat(results.getTestResults())
.extracting(TestResult::getPrecondition)
.containsExactly(value);
}

/**
* Tests can report precondition html independently from their description.
*/
@Description
@Test
void shouldAddPreconditionHtml() {
final String value = "<p>User is logged in</p>";

final AllureResults results = runWithinTestContext(() -> Allure.preconditionHtml(value));

assertThat(results.getTestResults())
.extracting(TestResult::getPreconditionHtml)
.containsExactly(value);
}

/**
* Tests can report expected result independently from their description.
*/
@Description
@Test
void shouldAddExpectedResult() {
final String value = "**Dashboard** is displayed";

final AllureResults results = runWithinTestContext(() -> Allure.expectedResult(value));

assertThat(results.getTestResults())
.extracting(TestResult::getExpectedResult)
.containsExactly(value);
}

/**
* Tests can report expected result html independently from their description.
*/
@Description
@Test
void shouldAddExpectedResultHtml() {
final String value = "<p>Dashboard is displayed</p>";

final AllureResults results = runWithinTestContext(() -> Allure.expectedResultHtml(value));

assertThat(results.getTestResults())
.extracting(TestResult::getExpectedResultHtml)
.containsExactly(value);
}

/**
* Preconditions and expected results set in shared setup become defaults for linked tests.
*/
@Description
@Test
void shouldInheritPreconditionAndExpectedResultFromBeforeFixture() {
final AllureResults results = runWithPreconditionAndExpectedResult(new TestResult());

final TestResult result = results.getTestResults().get(0);
assertThat(result)
.hasFieldOrPropertyWithValue("precondition", "Scope precondition")
.hasFieldOrPropertyWithValue("preconditionHtml", "<p>Scope precondition</p>")
.hasFieldOrPropertyWithValue("expectedResult", "Scope expected result")
.hasFieldOrPropertyWithValue("expectedResultHtml", "<p>Scope expected result</p>");
}

/**
* Explicit test metadata, including empty strings, takes precedence over shared setup defaults.
*/
@Description
@Test
void shouldPreserveExplicitPreconditionAndExpectedResult() {
final TestResult test = new TestResult()
.setPrecondition("")
.setPreconditionHtml("")
.setExpectedResult("")
.setExpectedResultHtml("");

final AllureResults results = runWithPreconditionAndExpectedResult(test);

final TestResult result = results.getTestResults().get(0);
assertThat(result)
.hasFieldOrPropertyWithValue("precondition", "")
.hasFieldOrPropertyWithValue("preconditionHtml", "")
.hasFieldOrPropertyWithValue("expectedResult", "")
.hasFieldOrPropertyWithValue("expectedResultHtml", "");
}

/**
* Each Markdown or HTML field inherits independently when a test overrides only part of its metadata.
*/
@Description
@Test
void shouldInheritOnlyMissingPreconditionAndExpectedResultFields() {
final TestResult test = new TestResult()
.setPrecondition("Test precondition")
.setExpectedResultHtml("<p>Test expected result</p>");

final AllureResults results = runWithPreconditionAndExpectedResult(test);

final TestResult result = results.getTestResults().get(0);
assertThat(result)
.hasFieldOrPropertyWithValue("precondition", "Test precondition")
.hasFieldOrPropertyWithValue("preconditionHtml", "<p>Scope precondition</p>")
.hasFieldOrPropertyWithValue("expectedResult", "Scope expected result")
.hasFieldOrPropertyWithValue("expectedResultHtml", "<p>Test expected result</p>");
}

private AllureResults runWithPreconditionAndExpectedResult(final TestResult test) {
return runTests(lifecycle -> {
final AllureExternalKey scopeKey = AllureExternalKey.of(
AllureTest.class, SCOPE_KEY_NAMESPACE, UUID.randomUUID().toString()
);
final AllureExternalKey fixtureKey = AllureExternalKey.of(
AllureTest.class, FIXTURE_KEY_NAMESPACE, UUID.randomUUID().toString()
);
final AllureExternalKey testKey = AllureExternalKey.of(
AllureTest.class, TEST_KEY_NAMESPACE, UUID.randomUUID().toString()
);
lifecycle.registerScope(scopeKey);
lifecycle.startBeforeFixture(scopeKey, fixtureKey, new FixtureResult().setName("before"));
Allure.precondition("Scope precondition");
Allure.preconditionHtml("<p>Scope precondition</p>");
Allure.expectedResult("Scope expected result");
Allure.expectedResultHtml("<p>Scope expected result</p>");
lifecycle.stopFixture(fixtureKey);

lifecycle.scheduleTest(List.of(scopeKey), testKey, test.setUuid(UUID.randomUUID().toString()).setName("test"));
lifecycle.startTest(testKey);
lifecycle.stopTest(testKey);
lifecycle.writeTest(testKey);
});
}

@Test
void shouldApplyRuntimeMetadataFromBeforeFixtureScope() {
final String scopeUuid = UUID.randomUUID().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
package io.qameta.allure;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.qameta.allure.internal.Allure2ModelJackson;
import io.qameta.allure.model.GlobalAttachment;
import io.qameta.allure.model.GlobalError;
import io.qameta.allure.model.Globals;
import io.qameta.allure.model.ScopeResult;
import io.qameta.allure.model.Status;
import io.qameta.allure.model.StatusDetails;
import io.qameta.allure.model.TestResult;
Expand Down Expand Up @@ -86,6 +88,81 @@ void shouldWriteTestResult(@TempDir final Path folder) {
.isRegularFile();
}

/**
* Result files preserve Markdown and HTML preconditions and expected results as separate JSON fields.
*/
@Description
@Test
void shouldWritePreconditionAndExpectedResult(@TempDir final Path folder) throws IOException {
final ObjectMapper mapper = Allure2ModelJackson.createMapper();
final String uuid = UUID.randomUUID().toString();
final TestResult result = new TestResult()
.setUuid(uuid)
.setPrecondition("User is **logged in**")
.setPreconditionHtml("<p>User is logged in</p>")
.setExpectedResult("**Dashboard** is displayed")
.setExpectedResultHtml("<p>Dashboard is displayed</p>");
final FileSystemResultsWriter writer = new FileSystemResultsWriter(folder);

writer.write(result);

final JsonNode payload = mapper.readTree(folder.resolve(generateTestResultName(uuid)).toFile());
assertThat(payload.path("precondition").asText()).isEqualTo(result.getPrecondition());
assertThat(payload.path("preconditionHtml").asText()).isEqualTo(result.getPreconditionHtml());
assertThat(payload.path("expectedResult").asText()).isEqualTo(result.getExpectedResult());
assertThat(payload.path("expectedResultHtml").asText()).isEqualTo(result.getExpectedResultHtml());
final TestResult restored = mapper.treeToValue(payload, TestResult.class);
assertThat(restored)
.usingRecursiveComparison()
.isEqualTo(result);
}

/**
* Scope metadata can round-trip all four fields without conflating Markdown and HTML content.
*/
@Description
@Test
void shouldSerializeScopePreconditionAndExpectedResult() throws IOException {
final ObjectMapper mapper = Allure2ModelJackson.createMapper();
final ScopeResult scope = new ScopeResult()
.setPrecondition("Scope precondition")
.setPreconditionHtml("<p>Scope precondition</p>")
.setExpectedResult("Scope expected result")
.setExpectedResultHtml("<p>Scope expected result</p>");

final String json = mapper.writeValueAsString(scope);

final JsonNode payload = mapper.readTree(json);
assertThat(payload.path("precondition").asText()).isEqualTo(scope.getPrecondition());
assertThat(payload.path("preconditionHtml").asText()).isEqualTo(scope.getPreconditionHtml());
assertThat(payload.path("expectedResult").asText()).isEqualTo(scope.getExpectedResult());
assertThat(payload.path("expectedResultHtml").asText()).isEqualTo(scope.getExpectedResultHtml());
final ScopeResult restored = mapper.readValue(json, ScopeResult.class);
assertThat(restored)
.usingRecursiveComparison()
.isEqualTo(scope);
}

/**
* Tests without preconditions or expected results do not emit null metadata fields.
*/
@Description
@Test
void shouldOmitUnsetPreconditionAndExpectedResult(@TempDir final Path folder) throws IOException {
final String uuid = UUID.randomUUID().toString();
final TestResult result = new TestResult().setUuid(uuid);
final FileSystemResultsWriter writer = new FileSystemResultsWriter(folder);

writer.write(result);

final JsonNode payload = Allure2ModelJackson.createMapper()
.readTree(folder.resolve(generateTestResultName(uuid)).toFile());
assertThat(payload.has("precondition")).isFalse();
assertThat(payload.has("preconditionHtml")).isFalse();
assertThat(payload.has("expectedResult")).isFalse();
assertThat(payload.has("expectedResultHtml")).isFalse();
}

@Test
void shouldWriteTestResultContainer(@TempDir final Path folder) {
FileSystemResultsWriter writer = new FileSystemResultsWriter(folder);
Expand Down
Loading
Loading