From 9f50f7bcea5ee205a84cdf84fce1c679013fd7ec Mon Sep 17 00:00:00 2001 From: qameta-ci Date: Mon, 15 Sep 2025 12:20:06 +0000 Subject: [PATCH 1/7] set next development version 2.31 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 01f88f2c..0ce9c63c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=2.30.0 +version=2.31-SNAPSHOT org.gradle.daemon=true org.gradle.parallel=true From 0206bdc433f1b778e0cc6332629da436720f772f Mon Sep 17 00:00:00 2001 From: epszaw Date: Wed, 17 Sep 2025 11:13:44 +0200 Subject: [PATCH 2/7] Add workflow-wide gh actions permissions (#1187) Co-authored-by: Maksim Stepanov <17935127+delatrie@users.noreply.github.com> --- .github/workflows/labeler.yml | 5 +++++ .github/workflows/labels-verify.yml | 5 +++++ .github/workflows/publish.yml | 3 +++ .github/workflows/release.yml | 5 +++++ 4 files changed, 18 insertions(+) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index e0d99fc8..f49976b5 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -3,9 +3,14 @@ name: "Set theme labels" on: - pull_request_target +permissions: + contents: read + jobs: triage: runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - uses: actions/labeler@v4 with: diff --git a/.github/workflows/labels-verify.yml b/.github/workflows/labels-verify.yml index 0c18ecb7..7315a905 100644 --- a/.github/workflows/labels-verify.yml +++ b/.github/workflows/labels-verify.yml @@ -4,9 +4,14 @@ on: pull_request_target: types: [opened, labeled, unlabeled, synchronize] +permissions: + contents: none + jobs: triage: runs-on: ubuntu-latest + permissions: + pull-requests: read steps: - uses: baev/action-label-verify@main with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f98763d2..97d4d7fb 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,9 @@ on: release: types: [ published ] +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5e31f9b5..6b08eb0e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,9 +11,14 @@ on: description: "The next version in . format WITHOUT SNAPSHOT SUFFIX" required: true +permissions: + contents: read + jobs: triage: runs-on: ubuntu-latest + permissions: + contents: write steps: - name: "Check release version" run: | From 1bf17c4c3c1fab8ae80544ebafa655dac6f89153 Mon Sep 17 00:00:00 2001 From: skuznetsov-al Date: Tue, 14 Oct 2025 12:34:06 +0300 Subject: [PATCH 3/7] rework grpc module (via #1199) --- allure-grpc/build.gradle.kts | 8 +- .../io/qameta/allure/grpc/AllureGrpc.java | 541 +++++++++++++----- .../io/qameta/allure/grpc/AllureGrpcTest.java | 344 +++++++++-- allure-grpc/src/test/proto/api.proto | 2 + 4 files changed, 697 insertions(+), 198 deletions(-) diff --git a/allure-grpc/build.gradle.kts b/allure-grpc/build.gradle.kts index 4bc8d356..259fc7e3 100644 --- a/allure-grpc/build.gradle.kts +++ b/allure-grpc/build.gradle.kts @@ -8,14 +8,16 @@ description = "Allure gRPC Integration" val agent: Configuration by configurations.creating -val grpcVersion = "1.57.2" -val protobufVersion = "4.27.3" +val grpcVersion = "1.75.0" +val protobufVersion = "4.32.1" +val jacksonVersion = "2.17.2" dependencies { agent("org.aspectj:aspectjweaver") api(project(":allure-attachments")) + compileOnly("com.fasterxml.jackson.core:jackson-annotations:$jacksonVersion") compileOnly("com.google.protobuf:protobuf-java-util:$protobufVersion") - compileOnly("io.grpc:grpc-core:$grpcVersion") + compileOnly("io.grpc:grpc-api:$grpcVersion") testImplementation("com.google.protobuf:protobuf-java-util:$protobufVersion") testImplementation("com.google.protobuf:protobuf-java:$protobufVersion") testImplementation("io.grpc:grpc-core:$grpcVersion") diff --git a/allure-grpc/src/main/java/io/qameta/allure/grpc/AllureGrpc.java b/allure-grpc/src/main/java/io/qameta/allure/grpc/AllureGrpc.java index 35fc4977..6518f534 100644 --- a/allure-grpc/src/main/java/io/qameta/allure/grpc/AllureGrpc.java +++ b/allure-grpc/src/main/java/io/qameta/allure/grpc/AllureGrpc.java @@ -27,21 +27,23 @@ import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.qameta.allure.Allure; +import io.qameta.allure.AllureLifecycle; import io.qameta.allure.attachment.AttachmentData; -import io.qameta.allure.attachment.AttachmentProcessor; -import io.qameta.allure.attachment.DefaultAttachmentProcessor; +import io.qameta.allure.attachment.AttachmentRenderer; import io.qameta.allure.attachment.FreemarkerAttachmentRenderer; +import io.qameta.allure.model.Attachment; import io.qameta.allure.model.Status; import io.qameta.allure.model.StepResult; -import io.qameta.allure.util.ResultsUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.UUID; - -import static java.util.Objects.requireNonNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Allure interceptor logger for gRPC. @@ -49,158 +51,433 @@ * @author dtuchs (Dmitry Tuchs). */ @SuppressWarnings({ - "checkstyle:ClassFanOutComplexity", - "checkstyle:AnonInnerLength", - "checkstyle:JavaNCSS" + "checkstyle:ClassFanOutComplexity", + "checkstyle:AnonInnerLength", + "checkstyle:JavaNCSS" }) public class AllureGrpc implements ClientInterceptor { private static final Logger LOGGER = LoggerFactory.getLogger(AllureGrpc.class); - private static final JsonFormat.Printer JSON_PRINTER = JsonFormat.printer(); - - private String requestTemplatePath = "grpc-request.ftl"; - private String responseTemplatePath = "grpc-response.ftl"; + private static final String UNKNOWN = "unknown"; + private static final String JSON_SUFFIX = " (json)"; + private static final JsonFormat.Printer GRPC_TO_JSON_PRINTER = JsonFormat.printer(); - private boolean markStepFailedOnNonZeroCode = true; - private boolean interceptResponseMetadata; - - public AllureGrpc setRequestTemplate(final String templatePath) { - this.requestTemplatePath = templatePath; - return this; - } + private final AllureLifecycle lifecycle; + private final boolean markStepFailedOnNonZeroCode; + private final boolean interceptResponseMetadata; + private final String requestTemplatePath; + private final String responseTemplatePath; - public AllureGrpc setResponseTemplate(final String templatePath) { - this.responseTemplatePath = templatePath; - return this; + public AllureGrpc() { + this(Allure.getLifecycle(), true, false, + "grpc-request.ftl", "grpc-response.ftl"); } - public AllureGrpc markStepFailedOnNonZeroCode(final boolean value) { - this.markStepFailedOnNonZeroCode = value; - return this; - } - - public AllureGrpc interceptResponseMetadata(final boolean value) { - this.interceptResponseMetadata = value; - return this; + public AllureGrpc( + final AllureLifecycle lifecycle, + final boolean markStepFailedOnNonZeroCode, + final boolean interceptResponseMetadata, + final String requestTemplatePath, + final String responseTemplatePath + ) { + this.lifecycle = lifecycle; + this.markStepFailedOnNonZeroCode = markStepFailedOnNonZeroCode; + this.interceptResponseMetadata = interceptResponseMetadata; + this.requestTemplatePath = requestTemplatePath; + this.responseTemplatePath = responseTemplatePath; } @Override - public ClientCall interceptCall(final MethodDescriptor method, - final CallOptions callOptions, - final Channel next) { - final AttachmentProcessor processor = new DefaultAttachmentProcessor(); + public ClientCall interceptCall( + final MethodDescriptor methodDescriptor, + final CallOptions callOptions, + final Channel nextChannel + ) { + final AllureLifecycle current = lifecycle; + final String parent = current.getCurrentTestCaseOrStep().orElse(null); + final String stepUuid = UUID.randomUUID().toString(); + final List clientMessages = new ArrayList<>(); + final List serverMessages = new ArrayList<>(); + final Map initialHeaders = new LinkedHashMap<>(); + final Map trailers = new LinkedHashMap<>(); - return new ForwardingClientCall.SimpleForwardingClientCall( - next.newCall(method, callOptions.withoutWaitForReady())) { + final String stepName = buildStepName(nextChannel, methodDescriptor); + if (parent != null) { + current.startStep(parent, stepUuid, new StepResult().setName(stepName)); + } else { + current.startStep(stepUuid, new StepResult().setName(stepName)); + } - private String stepUuid; - private final List parsedResponses = new ArrayList<>(); - - @Override - public void sendMessage(final T message) { - stepUuid = UUID.randomUUID().toString(); - Allure.getLifecycle().startStep(stepUuid, (new StepResult()).setName( - "Send gRPC request to " - + next.authority() - + trimGrpcMethodName(method.getFullMethodName()) - )); - try { - final GrpcRequestAttachment rpcRequestAttach = GrpcRequestAttachment.Builder - .create("gRPC request", method.getFullMethodName()) - .setBody(JSON_PRINTER.print((MessageOrBuilder) message)) - .build(); - processor.addAttachment(rpcRequestAttach, new FreemarkerAttachmentRenderer(requestTemplatePath)); - super.sendMessage(message); - } catch (InvalidProtocolBufferException e) { - LOGGER.warn("Can`t parse gRPC request", e); - } catch (Throwable e) { - Allure.getLifecycle().updateStep(stepResult -> - stepResult.setStatus(ResultsUtils.getStatus(e).orElse(Status.BROKEN)) - .setStatusDetails(ResultsUtils.getStatusDetails(e).orElse(null)) - ); - Allure.getLifecycle().stopStep(stepUuid); - stepUuid = null; - } - } + final StepContext stepContext = new StepContext<>( + stepUuid, methodDescriptor, current, clientMessages, + serverMessages, initialHeaders, trailers + ); + return new ForwardingClientCall.SimpleForwardingClientCall( + nextChannel.newCall(methodDescriptor, callOptions) + ) { @Override - public void start(final Listener responseListener, final Metadata headers) { - final ClientCall.Listener listener = new ForwardingClientCallListener() { + public void start(final Listener responseListener, final Metadata requestHeaders) { + final Listener forwardingListener = new ForwardingClientCallListener() { @Override - protected Listener delegate() { + protected Listener delegate() { return responseListener; } @Override - public void onClose(final io.grpc.Status status, final Metadata trailers) { - GrpcResponseAttachment.Builder responseAttachmentBuilder = null; - - if (parsedResponses.size() == 1) { - responseAttachmentBuilder = GrpcResponseAttachment.Builder - .create("gRPC response") - .setBody(parsedResponses.iterator().next()); - } else if (parsedResponses.size() > 1) { - responseAttachmentBuilder = GrpcResponseAttachment.Builder - .create("gRPC response (collection of elements from Server stream)") - .setBody("[" + String.join(",\n", parsedResponses) + "]"); - } - if (!status.isOk()) { - String description = status.getDescription(); - if (description == null) { - description = "No description provided"; - } - responseAttachmentBuilder = GrpcResponseAttachment.Builder - .create(status.getCode().name()) - .setStatus(description); - } - - requireNonNull(responseAttachmentBuilder).setStatus(status.toString()); - if (interceptResponseMetadata) { - for (String key : headers.keys()) { - requireNonNull(responseAttachmentBuilder).setMetadata( - key, - headers.get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER)) - ); - } - } - processor.addAttachment( - requireNonNull(responseAttachmentBuilder).build(), - new FreemarkerAttachmentRenderer(responseTemplatePath) - ); - - if (status.isOk() || !markStepFailedOnNonZeroCode) { - Allure.getLifecycle().updateStep(stepUuid, step -> step.setStatus(Status.PASSED)); - } else { - Allure.getLifecycle().updateStep(stepUuid, step -> step.setStatus(Status.FAILED)); - } - Allure.getLifecycle().stopStep(stepUuid); - stepUuid = null; - super.onClose(status, trailers); + public void onHeaders(final Metadata headers) { + handleHeaders(headers, stepContext.getInitialHeaders()); + super.onHeaders(headers); + } + + @Override + public void onMessage(final R message) { + handleServerMessage(message, stepContext.getServerMessages()); + super.onMessage(message); } @Override - public void onMessage(final A message) { - try { - parsedResponses.add(JSON_PRINTER.print((MessageOrBuilder) message)); - super.onMessage(message); - } catch (InvalidProtocolBufferException e) { - LOGGER.warn("Can`t parse gRPC response", e); - } catch (Throwable e) { - Allure.getLifecycle().updateStep(step -> - step.setStatus(ResultsUtils.getStatus(e).orElse(Status.BROKEN)) - .setStatusDetails(ResultsUtils.getStatusDetails(e).orElse(null)) - ); - Allure.getLifecycle().stopStep(stepUuid); - stepUuid = null; - } + public void onClose(final io.grpc.Status status, final Metadata responseTrailers) { + handleClose(status, responseTrailers, stepContext); + super.onClose(status, responseTrailers); } }; - super.start(listener, headers); + super.start(forwardingListener, requestHeaders); } - private String trimGrpcMethodName(final String source) { - return source.substring(source.lastIndexOf('/')); + @Override + public void sendMessage(final T message) { + handleClientMessage(message, stepContext.getClientMessages()); + super.sendMessage(message); } }; } + + private void addRawJsonAttachment( + final String stepUuid, + final String attachmentName, + final String jsonBody, + final AllureLifecycle lifecycle + ) { + if (jsonBody == null || jsonBody.isEmpty()) { + return; + } + final String source = UUID.randomUUID() + ".json"; + lifecycle.updateStep(stepUuid, step -> step.getAttachments().add( + new Attachment() + .setName(attachmentName) + .setSource(source) + .setType("application/json") + )); + lifecycle.writeAttachment( + source, + new ByteArrayInputStream(jsonBody.getBytes(StandardCharsets.UTF_8)) + ); + } + + private void handleClose( + final io.grpc.Status status, + final Metadata responseTrailers, + final StepContext stepContext + ) { + try { + if (interceptResponseMetadata && responseTrailers != null) { + copyAsciiResponseMetadata(responseTrailers, stepContext.getTrailers()); + } + attachRequestIfPresent( + stepContext.getStepUuid(), + stepContext.getMethodDescriptor(), + stepContext.getClientMessages(), + stepContext.getLifecycle() + ); + attachResponse( + stepContext.getStepUuid(), + stepContext.getServerMessages(), + status, + stepContext.getInitialHeaders(), + stepContext.getTrailers(), + stepContext.getLifecycle() + ); + stepContext.getLifecycle().updateStep( + stepContext.getStepUuid(), + step -> step.setStatus(convertStatus(status)) + ); + } catch (Throwable throwable) { + LOGGER.error("Failed to finalize Allure step for gRPC call", throwable); + stepContext.getLifecycle().updateStep( + stepContext.getStepUuid(), + step -> step.setStatus(Status.BROKEN) + ); + } finally { + stopStepSafely(stepContext.getLifecycle(), stepContext.getStepUuid()); + } + } + + private void handleHeaders(final Metadata headers, final Map destination) { + try { + if (interceptResponseMetadata && headers != null) { + copyAsciiResponseMetadata(headers, destination); + } + } catch (Throwable throwable) { + LOGGER.warn("Failed to capture response headers", throwable); + } + } + + private void handleClientMessage(final T message, final List destination) { + try { + destination.add(GRPC_TO_JSON_PRINTER.print((MessageOrBuilder) message)); + } catch (InvalidProtocolBufferException e) { + LOGGER.error("Could not serialize gRPC request message to JSON", e); + } catch (Throwable throwable) { + LOGGER.error("Unexpected error while serializing gRPC request message", throwable); + } + } + + private void handleServerMessage(final R message, final List destination) { + try { + destination.add(GRPC_TO_JSON_PRINTER.print((MessageOrBuilder) message)); + } catch (InvalidProtocolBufferException e) { + LOGGER.error("Could not serialize gRPC response message to JSON", e); + } catch (Throwable throwable) { + LOGGER.error("Unexpected error while serializing gRPC response message", throwable); + } + } + + private void attachRequestIfPresent( + final String stepUuid, + final MethodDescriptor methodDescriptor, + final List clientMessages, + final AllureLifecycle lifecycle + ) { + final String body = toJsonBody(clientMessages); + if (body == null) { + return; + } + final String name = clientMessages.size() > 1 + ? "gRPC request (collection of elements from Client stream)" + : "gRPC request"; + final GrpcRequestAttachment requestAttachment = GrpcRequestAttachment.Builder + .create(name, methodDescriptor.getFullMethodName()) + .setBody(body) + .build(); + + addRenderedAttachmentToStep( + stepUuid, + requestAttachment.getName(), + requestAttachment, + requestTemplatePath, + lifecycle + ); + addRawJsonAttachment(stepUuid, name + JSON_SUFFIX, body, lifecycle); + } + + private void attachResponse( + final String stepUuid, + final List serverMessages, + final io.grpc.Status status, + final Map initialHeaders, + final Map trailers, + final AllureLifecycle lifecycle + ) { + final String body = toJsonBody(serverMessages); + final String name = serverMessages.size() > 1 + ? "gRPC response (collection of elements from Server stream)" + : "gRPC response"; + + final Map metadata = new LinkedHashMap<>(); + if (interceptResponseMetadata) { + metadata.putAll(initialHeaders); + metadata.putAll(trailers); + } + + final GrpcResponseAttachment.Builder builder = GrpcResponseAttachment.Builder + .create(name) + .setStatus(status.toString()); + + if (body != null) { + builder.setBody(body); + } + if (!metadata.isEmpty()) { + builder.addMetadata(metadata); + } + + final GrpcResponseAttachment responseAttachment = builder.build(); + addRenderedAttachmentToStep( + stepUuid, + responseAttachment.getName(), + responseAttachment, + responseTemplatePath, + lifecycle + ); + if (body != null) { + addRawJsonAttachment(stepUuid, name + JSON_SUFFIX, body, lifecycle); + } + } + + private void stopStepSafely(final AllureLifecycle lifecycle, final String stepUuid) { + try { + lifecycle.stopStep(stepUuid); + } catch (Throwable throwable) { + LOGGER.warn("Failed to stop Allure step {}", stepUuid, throwable); + } + } + + private Status convertStatus(final io.grpc.Status grpcStatus) { + if (grpcStatus.isOk() || !markStepFailedOnNonZeroCode) { + return Status.PASSED; + } + return Status.FAILED; + } + + private static String buildStepName( + final Channel channel, + final MethodDescriptor methodDescriptor + ) { + final String authority = channel != null ? channel.authority() : null; + final String safeAuthority = authority != null ? authority : UNKNOWN; + final String type = toSnakeCase(methodDescriptor.getType()); + return "Send " + type + " gRPC request to " + + safeAuthority + "/" + methodDescriptor.getFullMethodName(); + } + + private static String toSnakeCase(final MethodDescriptor.MethodType methodType) { + if (methodType == null) { + return UNKNOWN; + } + return methodType.name().toLowerCase(Locale.ROOT); + } + + private void addRenderedAttachmentToStep( + final String stepUuid, + final String attachmentName, + final AttachmentData data, + final String templatePath, + final AllureLifecycle lifecycle + ) { + final AttachmentRenderer renderer = + new FreemarkerAttachmentRenderer(templatePath); + final io.qameta.allure.attachment.AttachmentContent content; + try { + content = renderer.render(data); + } catch (Throwable throwable) { + LOGGER.warn( + "Could not render attachment '{}' using template '{}'", + attachmentName, templatePath, throwable + ); + return; + } + if (content == null || content.getContent() == null) { + LOGGER.warn("Rendered attachment '{}' is empty; skipping", attachmentName); + return; + } + String fileExtension = content.getFileExtension(); + if (fileExtension == null || fileExtension.isEmpty()) { + fileExtension = ".html"; + } + final String source = UUID.randomUUID() + fileExtension; + lifecycle.updateStep( + stepUuid, + step -> step.getAttachments().add( + new Attachment() + .setName(attachmentName) + .setSource(source) + .setType( + content.getContentType() != null + ? content.getContentType() + : "text/html" + ) + ) + ); + lifecycle.writeAttachment( + source, + new ByteArrayInputStream(content.getContent().getBytes(StandardCharsets.UTF_8)) + ); + } + + private static String toJsonBody(final List items) { + if (items == null || items.isEmpty()) { + return null; + } + if (items.size() == 1) { + return items.get(0); + } + final String joined = String.join(",\n", items); + return "[" + joined + "]"; + } + + private static void copyAsciiResponseMetadata( + final Metadata source, + final Map target + ) { + for (String key : source.keys()) { + if (key == null) { + continue; + } + if (key.endsWith(Metadata.BINARY_HEADER_SUFFIX)) { + continue; + } + final Metadata.Key keyAscii = + Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); + final String value = source.get(keyAscii); + if (value != null) { + target.put(key, value); + } + } + } + + private static final class StepContext { + private final String stepUuid; + private final MethodDescriptor methodDescriptor; + private final AllureLifecycle lifecycle; + private final List clientMessages; + private final List serverMessages; + private final Map initialHeaders; + private final Map trailers; + + StepContext( + final String stepUuid, + final MethodDescriptor methodDescriptor, + final AllureLifecycle lifecycle, + final List clientMessages, + final List serverMessages, + final Map initialHeaders, + final Map trailers + ) { + this.stepUuid = stepUuid; + this.methodDescriptor = methodDescriptor; + this.lifecycle = lifecycle; + this.clientMessages = clientMessages; + this.serverMessages = serverMessages; + this.initialHeaders = initialHeaders; + this.trailers = trailers; + } + + String getStepUuid() { + return stepUuid; + } + + MethodDescriptor getMethodDescriptor() { + return methodDescriptor; + } + + AllureLifecycle getLifecycle() { + return lifecycle; + } + + List getClientMessages() { + return clientMessages; + } + + List getServerMessages() { + return serverMessages; + } + + Map getInitialHeaders() { + return initialHeaders; + } + + Map getTrailers() { + return trailers; + } + } } diff --git a/allure-grpc/src/test/java/io/qameta/allure/grpc/AllureGrpcTest.java b/allure-grpc/src/test/java/io/qameta/allure/grpc/AllureGrpcTest.java index 6c83b9f0..e316fc75 100644 --- a/allure-grpc/src/test/java/io/qameta/allure/grpc/AllureGrpcTest.java +++ b/allure-grpc/src/test/java/io/qameta/allure/grpc/AllureGrpcTest.java @@ -15,12 +15,17 @@ */ package io.qameta.allure.grpc; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.Status; import io.grpc.StatusRuntimeException; +import io.grpc.stub.StreamObserver; +import io.qameta.allure.Allure; import io.qameta.allure.model.Attachment; import io.qameta.allure.model.StepResult; +import io.qameta.allure.model.TestResult; import io.qameta.allure.test.AllureResults; import org.grpcmock.GrpcMock; import org.grpcmock.junit5.GrpcMockExtension; @@ -29,135 +34,348 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; +import java.util.Map; import java.util.Optional; import static io.qameta.allure.test.RunUtils.runWithinTestContext; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.grpcmock.GrpcMock.bidiStreamingMethod; +import static org.grpcmock.GrpcMock.clientStreamingMethod; import static org.grpcmock.GrpcMock.serverStreamingMethod; import static org.grpcmock.GrpcMock.unaryMethod; -/** - * @author dtuchs (Dmitry Tuchs). - */ @ExtendWith(GrpcMockExtension.class) class AllureGrpcTest { private static final String RESPONSE_MESSAGE = "Hello world!"; + private static final ObjectMapper JSON = new ObjectMapper(); - private ManagedChannel channel; - private TestServiceGrpc.TestServiceBlockingStub blockingStub; + private ManagedChannel managedChannel; @BeforeEach - void configureMock() { - channel = ManagedChannelBuilder.forAddress("localhost", GrpcMock.getGlobalPort()) - .usePlaintext() - .build(); - blockingStub = TestServiceGrpc.newBlockingStub(channel) - .withInterceptors(new AllureGrpc()); + void configureMockServer() { + managedChannel = ManagedChannelBuilder + .forAddress("localhost", GrpcMock.getGlobalPort()) + .usePlaintext() + .directExecutor() + .build(); GrpcMock.stubFor(unaryMethod(TestServiceGrpc.getCalculateMethod()) - .willReturn(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build())); + .willReturn(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build())); + GrpcMock.stubFor(serverStreamingMethod(TestServiceGrpc.getCalculateServerStreamMethod()) - .willReturn(asList( - Response.newBuilder().setMessage(RESPONSE_MESSAGE).build(), - Response.newBuilder().setMessage(RESPONSE_MESSAGE).build() - ))); + .willReturn(asList( + Response.newBuilder().setMessage(RESPONSE_MESSAGE).build(), + Response.newBuilder().setMessage(RESPONSE_MESSAGE).build() + ))); + + GrpcMock.stubFor(clientStreamingMethod(TestServiceGrpc.getCalculateClientStreamMethod()) + .willReturn(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build())); + + GrpcMock.stubFor(bidiStreamingMethod(TestServiceGrpc.getCalculateBidiStreamMethod()) + .willProxyTo(responseObserver -> new StreamObserver() { + @Override + public void onNext(Request request) { + responseObserver.onNext(Response.newBuilder().setMessage(RESPONSE_MESSAGE).build()); + } + @Override + public void onError(Throwable throwable) { + } + @Override + public void onCompleted() { + responseObserver.onCompleted(); + } + })); } @AfterEach void shutdownChannel() { - Optional.ofNullable(channel).ifPresent(ManagedChannel::shutdownNow); + Optional.ofNullable(managedChannel).ifPresent(ManagedChannel::shutdown); } @Test void shouldCreateRequestAttachment() { - final Request request = Request.newBuilder() - .setTopic("1") - .build(); + Request request = Request.newBuilder() + .setTopic("1") + .build(); + + Status errorStatus = Status.NOT_FOUND; + GrpcMock.stubFor(unaryMethod(TestServiceGrpc.getCalculateMethod()).willReturn(errorStatus)); - final AllureResults results = execute(request); + AllureResults allureResults = executeUnaryExpectingException(request); - assertThat(results.getTestResults().get(0).getSteps()) - .flatExtracting(StepResult::getAttachments) - .extracting(Attachment::getName) - .contains("gRPC request"); + assertThat(allureResults.getTestResults().get(0).getSteps().get(0).getStatus()) + .isEqualTo(io.qameta.allure.model.Status.FAILED); + + assertThat(allureResults.getTestResults().get(0).getSteps()) + .flatExtracting(StepResult::getAttachments) + .extracting(Attachment::getName) + .contains("gRPC request", "gRPC response"); } @Test void shouldCreateResponseAttachment() { - final Request request = Request.newBuilder() - .setTopic("1") - .build(); + Request request = Request.newBuilder() + .setTopic("1") + .build(); - final AllureResults results = execute(request); + AllureResults allureResults = executeUnary(request); - assertThat(results.getTestResults().get(0).getSteps()) - .flatExtracting(StepResult::getAttachments) - .extracting(Attachment::getName) - .contains("gRPC response"); + assertThat(allureResults.getTestResults().get(0).getSteps()) + .flatExtracting(StepResult::getAttachments) + .extracting(Attachment::getName) + .contains("gRPC response"); } @Test void shouldCreateResponseAttachmentForServerStreamingResponse() { - final Request request = Request.newBuilder() - .setTopic("1") - .build(); + Request request = Request.newBuilder() + .setTopic("1") + .build(); - final AllureResults results = executeStreaming(request); + AllureResults allureResults = executeServerStreaming(request); - assertThat(results.getTestResults().get(0).getSteps()) - .flatExtracting(StepResult::getAttachments) - .extracting(Attachment::getName) - .contains("gRPC response (collection of elements from Server stream)"); + assertThat(allureResults.getTestResults().get(0).getSteps()) + .flatExtracting(StepResult::getAttachments) + .extracting(Attachment::getName) + .contains("gRPC response (collection of elements from Server stream)"); } @Test void shouldCreateResponseAttachmentOnStatusException() { - final Status status = Status.NOT_FOUND; + Status notFoundStatus = Status.NOT_FOUND; + GrpcMock.stubFor(unaryMethod(TestServiceGrpc.getCalculateMethod()).willReturn(notFoundStatus)); + + Request request = Request.newBuilder() + .setTopic("2") + .build(); + + AllureResults allureResults = executeUnaryExpectingException(request); + + assertThat(allureResults.getTestResults().get(0).getSteps().get(0).getStatus()) + .isEqualTo(io.qameta.allure.model.Status.FAILED); + + assertThat(allureResults.getTestResults().get(0).getSteps()) + .flatExtracting(StepResult::getAttachments) + .extracting(Attachment::getName) + .contains("gRPC response"); + } + + @Test + void shouldCreateAttachmentsForClientStreamingWithAsynchronousStub() { + Request firstClientRequest = Request.newBuilder().setTopic("A").build(); + Request secondClientRequest = Request.newBuilder().setTopic("B").build(); + + runWithinTestContext(() -> { + TestServiceGrpc.TestServiceStub asynchronousStub = + TestServiceGrpc.newStub(managedChannel).withInterceptors(new AllureGrpc()); + + final List receivedResponses = new ArrayList(); + + Allure.step("async-root-client-stream", () -> { + StreamObserver responseObserver = new StreamObserver() { + @Override + public void onNext(Response value) { + receivedResponses.add(value); + } + @Override + public void onError(Throwable throwable) { + } + @Override + public void onCompleted() { + } + }; + + StreamObserver requestObserver = asynchronousStub.calculateClientStream(responseObserver); + requestObserver.onNext(firstClientRequest); + requestObserver.onNext(secondClientRequest); + requestObserver.onCompleted(); + }); + + assertThat(receivedResponses).hasSize(1); + assertThat(receivedResponses.get(0).getMessage()).isEqualTo(RESPONSE_MESSAGE); + }); + } + + @Test + void shouldCreateAttachmentsForBidirectionalStreamingWithAsynchronousStub() { + Request firstBidirectionalRequest = Request.newBuilder().setTopic("C").build(); + Request secondBidirectionalRequest = Request.newBuilder().setTopic("D").build(); + + runWithinTestContext(() -> { + TestServiceGrpc.TestServiceStub asynchronousStub = + TestServiceGrpc.newStub(managedChannel).withInterceptors(new AllureGrpc()); + + List receivedResponses = new ArrayList<>(); + + Allure.step("async-root-bidi-stream", () -> { + StreamObserver responseObserver = new StreamObserver() { + @Override public void onNext(Response value) { receivedResponses.add(value); } + @Override public void onError(Throwable throwable) { } + @Override public void onCompleted() { } + }; + + StreamObserver requestObserver = asynchronousStub.calculateBidiStream(responseObserver); + requestObserver.onNext(firstBidirectionalRequest); + requestObserver.onNext(secondBidirectionalRequest); + requestObserver.onCompleted(); + }); + + assertThat(receivedResponses).hasSize(2); + assertThat(receivedResponses.get(0).getMessage()).isEqualTo(RESPONSE_MESSAGE); + assertThat(receivedResponses.get(1).getMessage()).isEqualTo(RESPONSE_MESSAGE); + }); + } + + @Test + void unaryRequestBodyIsCapturedAsJsonObject() throws Exception { + GrpcMock.stubFor(unaryMethod(TestServiceGrpc.getCalculateMethod()) + .willReturn(Response.newBuilder().setMessage("ok").build())); + + Request request = Request.newBuilder().setTopic("topic-1").build(); + + AllureResults allureResults = runWithinTestContext(() -> { + TestServiceGrpc.TestServiceBlockingStub stub = + TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); + Response response = stub.calculate(request); + assertThat(response.getMessage()).isEqualTo("ok"); + }); + + String jsonPayload = readJsonAttachmentByName(allureResults, "gRPC request (json)"); + JsonNode actualJsonNode = JSON.readTree(jsonPayload); + JsonNode expectedJsonNode = JSON.createObjectNode().put("topic", "topic-1"); + + assertThat(actualJsonNode).isEqualTo(expectedJsonNode); + } + + @Test + void unaryResponseBodyIsCapturedAsJsonObject() throws Exception { GrpcMock.stubFor(unaryMethod(TestServiceGrpc.getCalculateMethod()) - .willReturn(status)); + .willReturn(Response.newBuilder().setMessage("hello-world").build())); - final Request request = Request.newBuilder() - .setTopic("2") - .build(); + Request request = Request.newBuilder().setTopic("x").build(); - final AllureResults results = executeException(request); + AllureResults allureResults = runWithinTestContext(() -> { + TestServiceGrpc.TestServiceBlockingStub stub = + TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); + Response response = stub.calculate(request); + assertThat(response.getMessage()).isEqualTo("hello-world"); + }); + + String jsonPayload = readJsonAttachmentByName(allureResults, "gRPC response (json)"); + JsonNode actualJsonNode = JSON.readTree(jsonPayload); + JsonNode expectedJsonNode = JSON.createObjectNode().put("message", "hello-world"); - assertThat(results.getTestResults().get(0).getSteps()) - .flatExtracting(StepResult::getAttachments) - .extracting(Attachment::getName) - .contains(status.getCode().name()); + assertThat(actualJsonNode).isEqualTo(expectedJsonNode); } - protected final AllureResults execute(final Request request) { + @Test + void serverStreamingResponseBodyIsJsonArrayInOrder() throws Exception { + GrpcMock.stubFor(serverStreamingMethod(TestServiceGrpc.getCalculateServerStreamMethod()) + .willReturn(asList( + Response.newBuilder().setMessage("first").build(), + Response.newBuilder().setMessage("second").build() + ))); + + Request request = Request.newBuilder().setTopic("stream-topic").build(); + + AllureResults allureResults = runWithinTestContext(() -> { + TestServiceGrpc.TestServiceBlockingStub stub = + TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); + Iterator responseIterator = stub.calculateServerStream(request); + assertThat(responseIterator.hasNext()).isTrue(); + assertThat(responseIterator.next().getMessage()).isEqualTo("first"); + assertThat(responseIterator.hasNext()).isTrue(); + assertThat(responseIterator.next().getMessage()).isEqualTo("second"); + assertThat(responseIterator.hasNext()).isFalse(); + }); + + String jsonPayload = readJsonAttachmentByName( + allureResults, "gRPC response (collection of elements from Server stream) (json)" + ); + JsonNode actualJsonArray = JSON.readTree(jsonPayload); + + assertThat(actualJsonArray.isArray()).isTrue(); + assertThat(actualJsonArray.size()).isEqualTo(2); + assertThat(actualJsonArray.get(0)).isEqualTo(JSON.createObjectNode().put("message", "first")); + assertThat(actualJsonArray.get(1)).isEqualTo(JSON.createObjectNode().put("message", "second")); + } + private static String readJsonAttachmentByName(AllureResults allureResults, String jsonAttachmentName) { + TestResult test = allureResults.getTestResults().get(0); + + Attachment matchedAttachment = flattenSteps(test.getSteps()).stream() + .flatMap(step -> step.getAttachments().stream()) + .filter(attachment -> jsonAttachmentName.equals(attachment.getName())) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Attachment not found: " + jsonAttachmentName)); + + String attachmentSourceKey = matchedAttachment.getSource(); + Map attachmentsContent = allureResults.getAttachments(); + byte[] rawAttachmentContent = attachmentsContent.get(attachmentSourceKey); + if (rawAttachmentContent == null) { + throw new IllegalStateException("Attachment content not found by source: " + attachmentSourceKey); + } + return new String(rawAttachmentContent, StandardCharsets.UTF_8); + } + + protected final AllureResults executeUnary(Request request) { return runWithinTestContext(() -> { try { - final Response response = blockingStub.calculate(request); + TestServiceGrpc.TestServiceBlockingStub stub = + TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); + Response response = stub.calculate(request); assertThat(response.getMessage()).isEqualTo(RESPONSE_MESSAGE); - } catch (Exception e) { - throw new RuntimeException("Could not execute request " + request, e); + } catch (Exception exception) { + throw new RuntimeException("Could not execute request " + request, exception); } }); } - protected final AllureResults executeStreaming(final Request request) { + + protected final AllureResults executeServerStreaming(Request request) { return runWithinTestContext(() -> { try { - Iterator responseIterator = blockingStub.calculateServerStream(request); + TestServiceGrpc.TestServiceBlockingStub stub = + TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); + Iterator responseIterator = stub.calculateServerStream(request); + int responseCount = 0; while (responseIterator.hasNext()) { assertThat(responseIterator.next().getMessage()).isEqualTo(RESPONSE_MESSAGE); + responseCount++; } - } catch (Exception e) { - throw new RuntimeException("Could not execute request " + request, e); + assertThat(responseCount).isEqualTo(2); + } catch (Exception exception) { + throw new RuntimeException("Could not execute request " + request, exception); } }); } - protected final AllureResults executeException(final Request request) { - return runWithinTestContext(() -> { - assertThatExceptionOfType(StatusRuntimeException.class).isThrownBy(() -> blockingStub.calculate(request)); - }); + protected final AllureResults executeUnaryExpectingException(Request request) { + return runWithinTestContext(() -> + assertThatExceptionOfType(StatusRuntimeException.class) + .isThrownBy(() -> { + TestServiceGrpc.TestServiceBlockingStub stub = + TestServiceGrpc.newBlockingStub(managedChannel).withInterceptors(new AllureGrpc()); + Response response = stub.calculate(request); + assertThat(response.getMessage()).isEqualTo("ok"); + }) + ); + } + + private static List flattenSteps(List rootSteps) { + List allSteps = new ArrayList<>(); + if (rootSteps == null) { + return allSteps; + } + for (StepResult step : rootSteps) { + allSteps.add(step); + allSteps.addAll(flattenSteps(step.getSteps())); + } + return allSteps; } } diff --git a/allure-grpc/src/test/proto/api.proto b/allure-grpc/src/test/proto/api.proto index 552e76f6..378bb087 100644 --- a/allure-grpc/src/test/proto/api.proto +++ b/allure-grpc/src/test/proto/api.proto @@ -6,6 +6,8 @@ option java_package = "io.qameta.allure.grpc"; service TestService { rpc Calculate (Request) returns (Response); rpc CalculateServerStream (Request) returns (stream Response); + rpc CalculateClientStream (stream Request) returns (Response); + rpc CalculateBidiStream (stream Request) returns (stream Response); } message Request { From 85f3ccd74791fd33d93beef8d29fa2087a206b89 Mon Sep 17 00:00:00 2001 From: a-simeshin <89605031+a-simeshin@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:54:27 +0300 Subject: [PATCH 4/7] bump spring-web to 6.2.12 and fix compatibility with new api (via #1161) --- allure-spring-web/build.gradle.kts | 6 +++++- .../java/io/qameta/allure/springweb/AllureRestTemplate.java | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/allure-spring-web/build.gradle.kts b/allure-spring-web/build.gradle.kts index 4b941222..b7956681 100644 --- a/allure-spring-web/build.gradle.kts +++ b/allure-spring-web/build.gradle.kts @@ -1,6 +1,6 @@ description = "Allure Spring Web Integration" -val springWebVersion = "5.3.37" +val springWebVersion = "6.2.12" dependencies { api(project(":allure-attachments")) @@ -29,3 +29,7 @@ tasks.jar { tasks.test { useJUnitPlatform() } + +tasks.compileJava { + options.release.set(17) +} \ No newline at end of file diff --git a/allure-spring-web/src/main/java/io/qameta/allure/springweb/AllureRestTemplate.java b/allure-spring-web/src/main/java/io/qameta/allure/springweb/AllureRestTemplate.java index 9f1daf98..2c302b0e 100644 --- a/allure-spring-web/src/main/java/io/qameta/allure/springweb/AllureRestTemplate.java +++ b/allure-spring-web/src/main/java/io/qameta/allure/springweb/AllureRestTemplate.java @@ -81,7 +81,7 @@ public ClientHttpResponse intercept(@NonNull final HttpRequest request, final by final HttpRequestAttachment.Builder requestAttachmentBuilder = HttpRequestAttachment.Builder .create("Request", request.getURI().toString()) - .setMethod(request.getMethodValue()) + .setMethod(request.getMethod().name()) .setHeaders(toMapConverter(request.getHeaders())); if (body.length != 0) { requestAttachmentBuilder.setBody(new String(body, StandardCharsets.UTF_8)); @@ -94,7 +94,7 @@ public ClientHttpResponse intercept(@NonNull final HttpRequest request, final by final HttpResponseAttachment responseAttachment = HttpResponseAttachment.Builder .create("Response") - .setResponseCode(clientHttpResponse.getRawStatusCode()) + .setResponseCode(clientHttpResponse.getStatusCode().value()) .setHeaders(toMapConverter(clientHttpResponse.getHeaders())) .setBody(StreamUtils.copyToString(clientHttpResponse.getBody(), StandardCharsets.UTF_8)) .build(); From a2072807c4c65d485455f160c61fec93ec972677 Mon Sep 17 00:00:00 2001 From: a-simeshin <89605031+a-simeshin@users.noreply.github.com> Date: Thu, 13 Nov 2025 14:01:11 +0300 Subject: [PATCH 5/7] document how to prevent breaking allure lifecycle for Steps inside Awaitility evaluations (fixes #1135, via #1158) --- allure-awaitility/readme.md | 7 +- .../ConditionListenersPositiveTest.java | 7 ++ .../GlobalSettingsNegativeTest.java | 1 + .../GlobalSettingsPositiveTest.java | 1 + .../awaitility/MultipleConditionsTest.java | 70 +++++++++++++++++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 allure-awaitility/src/test/java/io/qameta/allure/awaitility/MultipleConditionsTest.java diff --git a/allure-awaitility/readme.md b/allure-awaitility/readme.md index 5a93b6a4..1fcb3469 100644 --- a/allure-awaitility/readme.md +++ b/allure-awaitility/readme.md @@ -7,11 +7,16 @@ For more information about awaitility highly recommended look into [awaitility u ### Configuration examples -Single line for all awaitility conditions in project +Single line for all awaitility conditions in project ```java Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener()); ``` +And another line to prevent breaking allure lifecycle for Steps inside Awaitility evaluations +```java +Awaitility.pollInSameThread(); +``` + Moreover, it's possible logging only few unstable conditions with method `.conditionEvaluationListener()` ```java final AtomicInteger atomicInteger = new AtomicInteger(0); diff --git a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/ConditionListenersPositiveTest.java b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/ConditionListenersPositiveTest.java index 74cfa88f..1a3bad81 100644 --- a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/ConditionListenersPositiveTest.java +++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/ConditionListenersPositiveTest.java @@ -18,6 +18,8 @@ import io.qameta.allure.model.Status; import io.qameta.allure.model.StepResult; import io.qameta.allure.model.TestResult; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; @@ -38,6 +40,11 @@ class ConditionListenersPositiveTest { + @BeforeAll + static void setup() { + Awaitility.pollInSameThread(); + } + /** * Positive test to check proper allure steps generation. *

diff --git a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsNegativeTest.java b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsNegativeTest.java index 29577aee..5c169fd6 100644 --- a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsNegativeTest.java +++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsNegativeTest.java @@ -48,6 +48,7 @@ void reset() { @BeforeEach void setup() { + Awaitility.pollInSameThread(); Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener()); } diff --git a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsPositiveTest.java b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsPositiveTest.java index db00c41e..52a6b50d 100644 --- a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsPositiveTest.java +++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/GlobalSettingsPositiveTest.java @@ -48,6 +48,7 @@ void reset() { @BeforeEach void setup() { + Awaitility.pollInSameThread(); Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener()); } diff --git a/allure-awaitility/src/test/java/io/qameta/allure/awaitility/MultipleConditionsTest.java b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/MultipleConditionsTest.java new file mode 100644 index 00000000..36a74a16 --- /dev/null +++ b/allure-awaitility/src/test/java/io/qameta/allure/awaitility/MultipleConditionsTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2016-2024 Qameta Software Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.qameta.allure.awaitility; + +import io.qameta.allure.model.Status; +import io.qameta.allure.model.TestResult; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.*; + +import java.util.List; +import java.util.stream.Stream; + +import static io.qameta.allure.test.RunUtils.runWithinTestContext; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class MultipleConditionsTest { + + @AfterEach + void reset() { + Awaitility.reset(); + } + + @BeforeEach + void setup() { + Awaitility.pollInSameThread(); + Awaitility.setDefaultConditionEvaluationListener(new AllureAwaitilityListener()); + } + + @TestFactory + Stream bothAwaitilityStepsShouldAppearTest() { + final List testResult = runWithinTestContext(() -> { + await().with() + .alias("First waiting") + .until(() -> true); + await().with() + .alias("Second waiting") + .until(() -> true); + }, + AllureAwaitilityListener::setLifecycle + ).getTestResults(); + + return Stream.of( + DynamicTest.dynamicTest("Exactly 2 top level step for 2 awaitility condition", () -> + assertThat(testResult.get(0).getSteps()) + .describedAs("Allure TestResult contains exactly 2 top level step for 2 awaitility condition") + .hasSize(2) + ), + DynamicTest.dynamicTest("All top level step for all awaitility condition has PASSED", () -> + assertThat(testResult.get(0).getSteps()) + .describedAs("Allure TestResult contains all top level step for all awaitility with PASSED condition") + .allMatch(step -> Status.PASSED.equals(step.getStatus())) + ) + ); + } + +} From 480ec9db8d9f9b74bde66cde99cbb7a4e8c80818 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:05:33 +0000 Subject: [PATCH 6/7] build(deps): bump grpcVersion from 1.75.0 to 1.76.0 (via #1202) --- allure-grpc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/allure-grpc/build.gradle.kts b/allure-grpc/build.gradle.kts index 259fc7e3..7eab5267 100644 --- a/allure-grpc/build.gradle.kts +++ b/allure-grpc/build.gradle.kts @@ -8,7 +8,7 @@ description = "Allure gRPC Integration" val agent: Configuration by configurations.creating -val grpcVersion = "1.75.0" +val grpcVersion = "1.76.0" val protobufVersion = "4.32.1" val jacksonVersion = "2.17.2" From 7709e11703c251454b7e128cad2bf1ca917bbf59 Mon Sep 17 00:00:00 2001 From: qameta-ci Date: Thu, 13 Nov 2025 11:07:27 +0000 Subject: [PATCH 7/7] release 2.31.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0ce9c63c..4c932d12 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=2.31-SNAPSHOT +version=2.31.0 org.gradle.daemon=true org.gradle.parallel=true