diff --git a/allure-hamcrest/build.gradle.kts b/allure-hamcrest/build.gradle.kts
new file mode 100644
index 000000000..3907ff9a9
--- /dev/null
+++ b/allure-hamcrest/build.gradle.kts
@@ -0,0 +1,32 @@
+description = "Allure Hamcrest Assertions Integration"
+
+val agent: Configuration by configurations.creating
+
+dependencies {
+ agent("org.aspectj:aspectjweaver")
+ api(project(":allure-java-commons"))
+ compileOnly("org.aspectj:aspectjrt")
+ implementation("org.hamcrest:hamcrest")
+ testAnnotationProcessor(project(":allure-descriptions-javadoc"))
+ testImplementation("org.junit.jupiter:junit-jupiter-api")
+ testImplementation("org.junit.jupiter:junit-jupiter-params")
+ testImplementation("org.assertj:assertj-core")
+ testImplementation("org.slf4j:slf4j-simple")
+ testImplementation(project(":allure-java-commons-test"))
+ testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
+}
+
+tasks.jar {
+ manifest {
+ attributes(mapOf(
+ "Automatic-Module-Name" to "io.qameta.allure.hamcrest"
+ ))
+ }
+}
+
+tasks.test {
+ useJUnitPlatform()
+ doFirst {
+ jvmArgs("-javaagent:${agent.singleFile}")
+ }
+}
\ No newline at end of file
diff --git a/allure-hamcrest/src/main/java/io/qameta/allure/hamcrest/AllureHamcrestAssert.java b/allure-hamcrest/src/main/java/io/qameta/allure/hamcrest/AllureHamcrestAssert.java
new file mode 100644
index 000000000..cf836d8f1
--- /dev/null
+++ b/allure-hamcrest/src/main/java/io/qameta/allure/hamcrest/AllureHamcrestAssert.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2021 Qameta Software OÜ
+ *
+ * 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.hamcrest;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.AfterReturning;
+import io.qameta.allure.Allure;
+import io.qameta.allure.AllureLifecycle;
+import io.qameta.allure.model.Status;
+import io.qameta.allure.model.StepResult;
+import io.qameta.allure.util.ObjectUtils;
+import org.hamcrest.Matcher;
+import org.hamcrest.StringDescription;
+
+import java.util.UUID;
+
+import static io.qameta.allure.util.ResultsUtils.getStatus;
+
+/**
+ *
+ * Aspect "interceptor" for automatic logging to the Allure report.
+ *
+ *
+ * This aspect should work for all asserts that are in the Hamcrest library, since they all go through a single method
+ * to start the comparison.
+ *
+ *
+ * In addition to the standard comparisons that are already in the Hamcrest library, this aspect should work correctly
+ * with custom matchers if developers correctly implemented the describeMismatch and / or describeMismatchSafely
+ * methods in the TypeSafeMatcher class.
+ *
+ *
+ * @author a-simeshin (Simeshin Artem)
+ * @see org.hamcrest.TypeSafeMatcher
+ */
+@Aspect
+@SuppressWarnings("all")
+public class AllureHamcrestAssert {
+
+ private static InheritableThreadLocal lifecycle = new InheritableThreadLocal() {
+ @Override
+ protected AllureLifecycle initialValue() {
+ return Allure.getLifecycle();
+ }
+ };
+
+ public static AllureLifecycle getLifecycle() {
+ return lifecycle.get();
+ }
+
+ @Pointcut("execution(void org.hamcrest.MatcherAssert.**(..))")
+ public void initAssertThat() {
+ }
+
+ /**
+ *
+ * assertThat(String comment, Object actual, Matcher expected) - one and only one central entry point for all
+ * asserts. Based on this rule, you only need to log a method with three arguments.
+ *
+ *
+ * Even if there is no comment as the first argument, an empty string will be passed as the first argument.
+ *
+ *
+ * For example assertThat(value123.get(), is(equalTo("value123"))) will be proxied to the metod
+ * assertThat("", value123.get(), is(equalTo("value123")))
+ *
+ *
+ * @param joinPoint - entry point with args and method name
+ */
+ @Before("initAssertThat()")
+ public void catchAndStartStep(final JoinPoint joinPoint) {
+ if (joinPoint.getArgs().length == 3) {
+ final String reason = (String) joinPoint.getArgs()[0];
+ final String actual = ObjectUtils.toString(joinPoint.getArgs()[1]);
+ final StringDescription description = new StringDescription();
+ final String expecting = description.appendText("assert \"")
+ .appendText(actual)
+ .appendText("\" ")
+ .appendDescriptionOf((Matcher) joinPoint.getArgs()[2])
+ .toString();
+
+ getLifecycle().startStep(
+ UUID.randomUUID().toString(),
+ new StepResult()
+ .setName(reason.isEmpty() ? expecting : expecting + " | " + reason)
+ .setDescription("Hamcrest assert")
+ .setStatus(Status.PASSED)
+ );
+ }
+ }
+
+ @AfterThrowing(pointcut = "initAssertThat()", throwing = "e")
+ public void stepFailed(final Throwable e) {
+ getLifecycle().updateStep(s -> s.setStatus(getStatus(e).orElse(Status.BROKEN)));
+ getLifecycle().stopStep();
+ }
+
+ @AfterReturning(pointcut = "initAssertThat()")
+ public void stepStop() {
+ getLifecycle().updateStep(s -> s.setStatus(Status.PASSED));
+ getLifecycle().stopStep();
+ }
+
+ /**
+ * For tests only.
+ *
+ * @param allure allure lifecycle to set
+ */
+ public static void setLifecycle(final AllureLifecycle allure) {
+ lifecycle.set(allure);
+ }
+}
diff --git a/allure-hamcrest/src/main/resources/META-INF/aop-ajc.xml b/allure-hamcrest/src/main/resources/META-INF/aop-ajc.xml
new file mode 100644
index 000000000..5a9f73e6c
--- /dev/null
+++ b/allure-hamcrest/src/main/resources/META-INF/aop-ajc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestAssertionNameContainsReasonTest.java b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestAssertionNameContainsReasonTest.java
new file mode 100644
index 000000000..6086258f9
--- /dev/null
+++ b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestAssertionNameContainsReasonTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2021 Qameta Software OÜ
+ *
+ * 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.hamcrest;
+
+import io.qameta.allure.model.StepResult;
+import io.qameta.allure.model.TestResult;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+
+import static io.qameta.allure.test.RunUtils.runWithinTestContext;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalToIgnoringCase;
+
+/**
+ * This tests should cover cases when reason string exists in assertion.
+ */
+@SuppressWarnings("all")
+@TestInstance(TestInstance.Lifecycle.PER_METHOD)
+public class AllureHamcrestAssertionNameContainsReasonTest {
+
+ @Test
+ void hamcrestAssertNameWithComment() {
+ final TestResult testResult = runWithinTestContext(
+ () -> assertThat(
+ "Business always likes something weird",
+ "TheBiscuit",
+ equalToIgnoringCase("thebiscuit")
+ ),
+ AllureHamcrestAssert::setLifecycle
+ ).getTestResults().get(0);
+
+ Assertions.assertThat(testResult.getSteps())
+ .flatExtracting(StepResult::getName)
+ .containsExactly("assert \"TheBiscuit\" a string equal to \"thebiscuit\" ignoring case | Business always likes something weird");
+ }
+
+ @Test
+ void hamcrestAssertNameWoComment() {
+ final TestResult testResult = runWithinTestContext(
+ () -> assertThat(
+ "TheBiscuit",
+ equalToIgnoringCase("thebiscuit")
+ ),
+ AllureHamcrestAssert::setLifecycle
+ ).getTestResults().get(0);
+
+ Assertions.assertThat(testResult.getSteps())
+ .flatExtracting(StepResult::getName)
+ .containsExactly("assert \"TheBiscuit\" a string equal to \"thebiscuit\" ignoring case");
+ }
+}
diff --git a/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestCollectionsMatchersTest.java b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestCollectionsMatchersTest.java
new file mode 100644
index 000000000..805b593c4
--- /dev/null
+++ b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestCollectionsMatchersTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2021 Qameta Software OÜ
+ *
+ * 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.hamcrest;
+
+import io.qameta.allure.model.StepResult;
+import io.qameta.allure.model.TestResult;
+import org.assertj.core.api.Assertions;
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import static io.qameta.allure.test.RunUtils.runWithinTestContext;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+/**
+ * All tests should cover http://hamcrest.org/JavaHamcrest/tutorial "Collections" section
+ *
+ * array
+ * hasEntry
+ * hasKey
+ * hasValue
+ * hasItem
+ * hasItems
+ * hasItemInArray
+ *
+ */
+@SuppressWarnings("all")
+@TestInstance(TestInstance.Lifecycle.PER_METHOD)
+public class AllureHamcrestCollectionsMatchersTest {
+
+ @Test
+ void hamcrestAssertNameForArrayMatchers() {
+ final TestResult testResult = runWithinTestContext(
+ () -> assertThat(new Integer[]{1,2,3}, is(array(equalTo(1), equalTo(2), equalTo(3)))),
+ AllureHamcrestAssert::setLifecycle
+ ).getTestResults().get(0);
+
+ Assertions.assertThat(testResult.getSteps())
+ .flatExtracting(StepResult::getName)
+ .containsExactly("assert \"[1, 2, 3]\" is [<1>, <2>, <3>]");
+ }
+
+ private static Stream mapTestCases() {
+ HashMap map = new HashMap();
+ map.put("key1", 1);
+ map.put("key2", 2);
+ map.put("key3", 3);
+
+ return Stream.of(
+ Arguments.of(
+ map, hasEntry(equalTo("key1"), equalTo(1)),
+ "assert \"{key1=1, key2=2, key3=3}\" map containing [\"key1\"-><1>]"
+ ),
+ Arguments.of(
+ map, hasKey(equalTo("key2")),
+ "assert \"{key1=1, key2=2, key3=3}\" map containing [\"key2\"->ANYTHING]"
+ ),
+ Arguments.of(
+ map, hasValue(equalTo(3)),
+ "assert \"{key1=1, key2=2, key3=3}\" map containing [ANYTHING-><3>]"
+ )
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("mapTestCases")
+ void hamcrestAssertNameForMapMatchers(Map actual, Matcher matcher, String expectedName) {
+ final TestResult testResult = runWithinTestContext(
+ () -> assertThat(actual, matcher),
+ AllureHamcrestAssert::setLifecycle
+ ).getTestResults().get(0);
+
+ Assertions.assertThat(testResult.getSteps())
+ .flatExtracting(StepResult::getName)
+ .containsExactly(expectedName);
+ }
+
+ private static Stream iterableTestCases() {
+ List list = Arrays.asList("val1", "val2", "val3");
+
+ return Stream.of(
+ Arguments.of(
+ list, hasItem(equalTo("key1")),
+ "assert \"[val1, val2, val3]\" a collection containing \"key1\""
+ ),
+ Arguments.of(
+ list, hasItems(startsWith("v"), endsWith("l2")),
+ "assert \"[val1, val2, val3]\" (a collection containing a string starting with \"v\" and a collection containing a string ending with \"l2\")"
+ ),
+ Arguments.of(
+ list, hasItemInArray(Arrays.asList("val1", "val2")),
+ "assert \"[val1, val2, val3]\" an array containing <[val1, val2]>"
+ )
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("iterableTestCases")
+ void hamcrestAssertNameForIterableMatchers(Iterable actual, Matcher matcher, String expectedName) {
+ final TestResult testResult = runWithinTestContext(
+ () -> assertThat(actual, matcher),
+ AllureHamcrestAssert::setLifecycle
+ ).getTestResults().get(0);
+
+ Assertions.assertThat(testResult.getSteps())
+ .flatExtracting(StepResult::getName)
+ .containsExactly(expectedName);
+ }
+}
diff --git a/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestLogicalMatchersTest.java b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestLogicalMatchersTest.java
new file mode 100644
index 000000000..f2c810425
--- /dev/null
+++ b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestLogicalMatchersTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2021 Qameta Software OÜ
+ *
+ * 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.hamcrest;
+
+import io.qameta.allure.model.StepResult;
+import io.qameta.allure.model.TestResult;
+import org.assertj.core.api.Assertions;
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static io.qameta.allure.test.RunUtils.runWithinTestContext;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+/**
+ * All tests should cover http://hamcrest.org/JavaHamcrest/tutorial "Logical" section
+ *
+ * allOf
+ * anyOf
+ * not
+ *
+ */
+@SuppressWarnings("all")
+@TestInstance(TestInstance.Lifecycle.PER_METHOD)
+public class AllureHamcrestLogicalMatchersTest {
+
+ private static Stream testCases() {
+ return Stream.of(
+ Arguments.of(
+ "thebiscuit", allOf(startsWith("the"), containsString("biscuit")),
+ "assert \"thebiscuit\" (a string starting with \"the\" and a string containing \"biscuit\")"
+ ),
+ Arguments.of(
+ "thebiscuit", anyOf(startsWith("the"), containsString("test")),
+ "assert \"thebiscuit\" (a string starting with \"the\" or a string containing \"test\")"
+ ),
+ Arguments.of(
+ "test", is(not(anyOf(startsWith("the"), containsString("biscuit")))),
+ "assert \"test\" is not (a string starting with \"the\" or a string containing \"biscuit\")"
+ )
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("testCases")
+ void hamcrestAssertNameForLogicalMatchers(String actual, Matcher matcher, String expectedName) {
+ final TestResult testResult = runWithinTestContext(
+ () -> assertThat(actual, matcher),
+ AllureHamcrestAssert::setLifecycle
+ ).getTestResults().get(0);
+
+ Assertions.assertThat(testResult.getSteps())
+ .flatExtracting(StepResult::getName)
+ .containsExactly(expectedName);
+ }
+}
diff --git a/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestNumberMatchersTest.java b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestNumberMatchersTest.java
new file mode 100644
index 000000000..c6d43e44c
--- /dev/null
+++ b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestNumberMatchersTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2021 Qameta Software OÜ
+ *
+ * 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.hamcrest;
+
+import io.qameta.allure.model.StepResult;
+import io.qameta.allure.model.TestResult;
+import org.assertj.core.api.Assertions;
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static io.qameta.allure.test.RunUtils.runWithinTestContext;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+/**
+ * All tests should cover http://hamcrest.org/JavaHamcrest/tutorial "Number" section
+ *
+ * closeTo
+ * greaterThan
+ * greaterThanOrEqualTo
+ * lessThan
+ * lessThanOrEqualTo
+ *
+ */
+@SuppressWarnings("all")
+@TestInstance(TestInstance.Lifecycle.PER_METHOD)
+public class AllureHamcrestNumberMatchersTest {
+
+ private static Stream testCases() {
+ return Stream.of(
+ Arguments.of(
+ 0.434d, is(closeTo(0.41d, 0.45d)),
+ "assert \"0.434\" is a numeric value within <0.45> of <0.41>"
+ ),
+ Arguments.of(
+ 0.434d, is(greaterThan(0.41d)),
+ "assert \"0.434\" is a value greater than <0.41>"
+ ),
+ Arguments.of(
+ 0.434d, is(greaterThanOrEqualTo(0.41d)),
+ "assert \"0.434\" is a value equal to or greater than <0.41>"
+ ),
+ Arguments.of(
+ 0.41d, is(lessThan(0.434d)),
+ "assert \"0.41\" is a value less than <0.434>"
+ ),
+ Arguments.of(
+ 0.41d, is(lessThanOrEqualTo(0.434d)),
+ "assert \"0.41\" is a value less than or equal to <0.434>"
+ )
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("testCases")
+ void hamcrestAssertNameForNumberMatchers(double actual, Matcher matcher, String expectedName) {
+ final TestResult testResult = runWithinTestContext(
+ () -> assertThat(actual, matcher),
+ AllureHamcrestAssert::setLifecycle
+ ).getTestResults().get(0);
+
+ Assertions.assertThat(testResult.getSteps())
+ .flatExtracting(StepResult::getName)
+ .containsExactly(expectedName);
+ }
+}
diff --git a/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestObjectMatchersTest.java b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestObjectMatchersTest.java
new file mode 100644
index 000000000..3b0170d24
--- /dev/null
+++ b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestObjectMatchersTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2021 Qameta Software OÜ
+ *
+ * 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.hamcrest;
+
+import io.qameta.allure.model.StepResult;
+import io.qameta.allure.model.TestResult;
+import org.assertj.core.api.Assertions;
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Arrays;
+import java.util.stream.Stream;
+
+import static io.qameta.allure.test.RunUtils.runWithinTestContext;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+/**
+ * All tests should cover http://hamcrest.org/JavaHamcrest/tutorial "Object" section
+ *
+ * equalTo
+ * hasToString
+ * instanceOf
+ * isCompatibleType in new version typeCompatibleWith
+ * notNullValue
+ * nullValue
+ * sameInstance
+ *
+ */
+@SuppressWarnings("all")
+@TestInstance(TestInstance.Lifecycle.PER_METHOD)
+public class AllureHamcrestObjectMatchersTest {
+
+ private static Stream testCases() {
+ final String testVal = "test_test";
+
+ return Stream.of(
+ Arguments.of(
+ "thebiscuit", is(equalTo("thebiscuit")),
+ "assert \"thebiscuit\" is \"thebiscuit\""
+ ),
+ Arguments.of(
+ true, hasToString("TRUE"),
+ "assert \"true\" with toString() \"TRUE\""
+ ),
+ Arguments.of(
+ Arrays.asList("key1", "key2"), instanceOf(Iterable.class),
+ "assert \"[key1, key2]\" an instance of java.lang.Iterable"
+ ),
+ Arguments.of(
+ Integer.class, typeCompatibleWith(Number.class),
+ "assert \"class java.lang.Integer\" type < java.lang.Number"
+ ),
+ Arguments.of(
+ "test", is(notNullValue()),
+ "assert \"test\" is not null"
+ ),
+ Arguments.of(
+ "test", is(not(nullValue())),
+ "assert \"test\" is not null"
+ ),
+ Arguments.of(
+ testVal, is(sameInstance(testVal)),
+ "assert \"test_test\" is sameInstance(\"test_test\")"
+ )
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("testCases")
+ void hamcrestAssertNameForObjectMatchers(Object actual, Matcher matcher, String expectedName) {
+ final TestResult testResult = runWithinTestContext(
+ () -> assertThat(actual, matcher),
+ AllureHamcrestAssert::setLifecycle
+ ).getTestResults().get(0);
+
+ Assertions.assertThat(testResult.getSteps())
+ .flatExtracting(StepResult::getName)
+ .containsExactly(expectedName);
+ }
+}
diff --git a/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestTextMatchersTest.java b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestTextMatchersTest.java
new file mode 100644
index 000000000..ec401a53b
--- /dev/null
+++ b/allure-hamcrest/src/test/java/io/qameta/allure/hamcrest/AllureHamcrestTextMatchersTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2021 Qameta Software OÜ
+ *
+ * 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.hamcrest;
+
+import io.qameta.allure.model.StepResult;
+import io.qameta.allure.model.TestResult;
+import org.assertj.core.api.Assertions;
+import org.hamcrest.Matcher;
+import org.junit.jupiter.api.TestInstance;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static io.qameta.allure.test.RunUtils.runWithinTestContext;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+/**
+ * All tests should cover http://hamcrest.org/JavaHamcrest/tutorial "Text" section
+ *
+ * equalToIgnoringCase
+ * equalToIgnoringWhiteSpace(deprecated) - suggesting in code to use equalToCompressingWhiteSpace
+ * containsString
+ * endsWith
+ * startsWith
+ *
+ */
+@SuppressWarnings("all")
+@TestInstance(TestInstance.Lifecycle.PER_METHOD)
+public class AllureHamcrestTextMatchersTest {
+
+ private static Stream testCases() {
+ return Stream.of(
+ Arguments.of(
+ "thebiscuit", equalToIgnoringCase("TheBiscuit"),
+ "assert \"thebiscuit\" a string equal to \"TheBiscuit\" ignoring case"
+ ),
+ Arguments.of(
+ "The Biscuit", equalToIgnoringWhiteSpace("TheBiscuit"),
+ "assert \"The Biscuit\" a string equal to \"TheBiscuit\" compressing white space"
+ ),
+ Arguments.of(
+ "The Biscuit", equalToCompressingWhiteSpace("TheBiscuit"),
+ "assert \"The Biscuit\" a string equal to \"TheBiscuit\" compressing white space"
+ ),
+ Arguments.of(
+ "The Biscuit", containsString("TheBiscuit"),
+ "assert \"The Biscuit\" a string containing \"TheBiscuit\""
+ ),
+ Arguments.of(
+ "The Biscuit", endsWith("Biscuit"),
+ "assert \"The Biscuit\" a string ending with \"Biscuit\""
+ ),
+ Arguments.of(
+ "The Biscuit", startsWith("Biscuit"),
+ "assert \"The Biscuit\" a string starting with \"Biscuit\""
+ )
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("testCases")
+ void hamcrestAssertNameForTextMatchers(String actual, Matcher matcher, String expectedName) {
+ final TestResult testResult = runWithinTestContext(
+ () -> assertThat(actual, matcher),
+ AllureHamcrestAssert::setLifecycle
+ ).getTestResults().get(0);
+
+ Assertions.assertThat(testResult.getSteps())
+ .flatExtracting(StepResult::getName)
+ .containsExactly(expectedName);
+ }
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index 56f72db2c..c39bbf257 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -141,6 +141,7 @@ configure(libs) {
dependency("commons-io:commons-io:2.11.0")
dependency("io.github.benas:random-beans:3.9.0")
dependency("io.github.glytching:junit-extensions:2.4.0")
+ dependency("org.hamcrest:hamcrest:2.2")
dependency("org.apache.commons:commons-lang3:3.12.0")
dependency("org.apache.httpcomponents:httpclient:4.5.13")
dependency("org.aspectj:aspectjrt:1.9.7")
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 34c483a40..f0b8302ea 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -11,6 +11,7 @@ include("allure-cucumber4-jvm")
include("allure-cucumber5-jvm")
include("allure-cucumber6-jvm")
include("allure-descriptions-javadoc")
+include("allure-hamcrest")
include("allure-httpclient")
include("allure-java-commons")
include("allure-java-commons-test")