horizon-test: Support for Parallel execution & report fix.

main
abdullah.masood 2025-12-08 22:25:12 +05:00
parent 823c84c95d
commit 823c8cfcd5
5 changed files with 68 additions and 21 deletions

View File

@ -13,27 +13,31 @@ import com.utopiadeals.framework.BrowserContextManager;
import org.junit.jupiter.api.extension.*; import org.junit.jupiter.api.extension.*;
import java.io.File; import java.io.File;
import java.lang.reflect.Method;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class ExtentReportExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, TestWatcher, TestExecutionExceptionHandler { public class ExtentReportExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback, TestWatcher, TestExecutionExceptionHandler {
private static ExtentReports extent; private static ExtentReports extent;
private static final Map<String, ExtentTest> suite = new ConcurrentHashMap<>();
private static final ThreadLocal<ExtentTest> test = new ThreadLocal<>(); private static final ThreadLocal<ExtentTest> test = new ThreadLocal<>();
@Override @Override
public void beforeAll(ExtensionContext context) throws Exception { public void beforeAll(ExtensionContext context) {
if (extent == null) { if (extent == null) {
createExtentReports(); createExtentReports();
} }
suite.computeIfAbsent(getClassName(context), k -> extent.createTest(getClassName(context)));
} }
@Override @Override
public void afterAll(ExtensionContext context) throws Exception { public void afterAll(ExtensionContext context) {
if (extent != null) { if (extent != null) {
extent.flush(); extent.flush();
} }
@ -43,18 +47,28 @@ public class ExtentReportExtension implements BeforeAllCallback, AfterAllCallbac
@Override @Override
public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable { public void handleTestExecutionException(ExtensionContext context, Throwable throwable) throws Throwable {
// This is called WHEN the exception occurs, before @AfterEach // This is called WHEN the exception occurs, before @AfterEach
captureFailureScreenshot(test.get(),throwable.getCause()); captureFailureScreenshot(test.get(), throwable.getCause());
throw throwable; // Re-throw to maintain normal failure flow throw throwable; // Re-throw to maintain normal failure flow
} }
@Override @Override
public void beforeEach(ExtensionContext context) throws Exception { public void beforeEach(ExtensionContext context) {
String className = context.getTestClass().map(Class::getSimpleName).orElse("Unknown"); String className = getClassName(context);
String testName = context.getTestMethod().map(m -> m.getName()).orElse("Unknown Test"); String methodName = getMethodName(context);
ExtentTest extentTest = extent.createTest(testName) ExtentTest testSuite = suite.get(className);
.assignCategory(className); if (testSuite != null) {
test.set(extentTest); ExtentTest extentTestNode = testSuite.createNode(methodName);
test.set(extentTestNode);
}
}
@Override
public void afterEach(ExtensionContext context) {
// Do not clear the ThreadLocal here because JUnit invokes TestWatcher callbacks
// (testSuccessful/testFailed/testAborted) after AfterEach, which would make test.get() null
// inside those callbacks and cause NPEs. The ThreadLocal will be overwritten in beforeEach
// for the next test method and cleared at JVM end.
} }
private void createExtentReports() { private void createExtentReports() {
@ -118,7 +132,7 @@ public class ExtentReportExtension implements BeforeAllCallback, AfterAllCallbac
// Attach screenshot to the failed test // Attach screenshot to the failed test
extentTest.fail(cause, extentTest.fail(cause,
MediaEntityBuilder.createScreenCaptureFromBase64String(base64Screenshot,"Application Screenshot").build() MediaEntityBuilder.createScreenCaptureFromBase64String(base64Screenshot, "Application Screenshot").build()
); );
extentTest.info("Page URL: " + page.url()); extentTest.info("Page URL: " + page.url());
@ -152,5 +166,17 @@ public class ExtentReportExtension implements BeforeAllCallback, AfterAllCallbac
} }
} }
private String getClassName(ExtensionContext context) {
return context.getTestClass()
.map(Class::getSimpleName)
.orElse("UnknownClass");
}
private String getMethodName(ExtensionContext context) {
return context.getTestMethod()
.map(Method::getName)
.orElse("UnknownMethod");
}
} }

View File

@ -1,17 +1,23 @@
package apitestsuites; package apitestsuites;
import com.utopiadeals.api.RestWebCall; import com.utopiadeals.api.RestWebCall;
import com.utopiadeals.framework.extensions.ExtentReportExtension;
import com.utopiadeals.utils.config.Constants; import com.utopiadeals.utils.config.Constants;
import io.restassured.response.Response; import io.restassured.response.Response;
import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import java.util.List; import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
@ExtendWith(ExtentReportExtension.class)
@Tag("access-mgmt-api") @Tag("access-mgmt-api")
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class AccessManagementApiTest { public class AccessManagementApiTest {
private static final String BASE_URL = Constants.getApiBaseUrl(); private static final String BASE_URL = Constants.getApiBaseUrl();
private static final String USERS_ENDPOINT = "/api/access-management/users"; private static final String USERS_ENDPOINT = "/api/access-management/users";
@ -20,6 +26,7 @@ public class AccessManagementApiTest {
private static Integer userId; private static Integer userId;
@Test @Test
@Order(1)
public void addUser_WithValidData_ShouldReturnSuccess() { public void addUser_WithValidData_ShouldReturnSuccess() {
// Arrange // Arrange
apiClient = new RestWebCall(BASE_URL, Constants.getApiTestUserName(), Constants.getApiTestPassword()); apiClient = new RestWebCall(BASE_URL, Constants.getApiTestUserName(), Constants.getApiTestPassword());
@ -50,6 +57,7 @@ public class AccessManagementApiTest {
} }
@Test @Test
@Order(2)
public void getUsers_WithValidEmailId_ShouldReturnSuccess() { public void getUsers_WithValidEmailId_ShouldReturnSuccess() {
// Act // Act
apiClient = new RestWebCall(BASE_URL, Constants.getApiTestUserName(), Constants.getApiTestPassword()); apiClient = new RestWebCall(BASE_URL, Constants.getApiTestUserName(), Constants.getApiTestPassword());
@ -62,6 +70,7 @@ public class AccessManagementApiTest {
} }
@Test @Test
@Order(3)
public void getUsers_WithValidId_ShouldReturnSuccess() { public void getUsers_WithValidId_ShouldReturnSuccess() {
// Act // Act
apiClient = new RestWebCall(BASE_URL, Constants.getApiTestUserName(), Constants.getApiTestPassword()); apiClient = new RestWebCall(BASE_URL, Constants.getApiTestUserName(), Constants.getApiTestPassword());

View File

@ -4,23 +4,23 @@ import com.utopiadeals.framework.annotations.JsonTestDataExtension;
import com.utopiadeals.utils.TestDataProvider; import com.utopiadeals.utils.TestDataProvider;
import com.utopiadeals.web.pages.LoginPage; import com.utopiadeals.web.pages.LoginPage;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Tag;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
public class LoginTest extends WebTestSuiteBase{ public class LoginTest extends WebTestSuiteBase {
@DisplayName("Login Test") @Tag("LoginTest")
@ParameterizedTest() @ParameterizedTest()
@JsonTestDataExtension("dataSet-0,dataSet-1") @JsonTestDataExtension("dataSet-0,dataSet-1")
public void loginTest(TestDataProvider testDataProvider){ public void loginTest(TestDataProvider testDataProvider) {
LoginPage loginPage = new LoginPage(); LoginPage loginPage = new LoginPage();
//loginPage.navigate("https://app.sellercosmos.com/"); //loginPage.navigate("https://app.sellercosmos.com/");
String userName = testDataProvider.getString("username"); String userName = testDataProvider.getString("username");
String password = testDataProvider.getString("password"); String password = testDataProvider.getString("password");
loginPage.login(userName,password); loginPage.login(userName, password);
// loginPage.login("utest@utopiadeals.com222222","utest0001"); // loginPage.login("utest@utopiadeals.com222222","utest0001");
Assertions.assertTrue(false,"Expecting true but found false"); Assertions.assertTrue(false, "Expecting true but found false");
} }
} }

View File

@ -0,0 +1,12 @@
junit.jupiter.execution.parallel.enabled = true
# Run test METHODS sequentially within each class to preserve intended order
junit.jupiter.execution.parallel.mode.default = same_thread
# Allow different test classes to run in parallel
junit.jupiter.execution.parallel.mode.classes.default = concurrent
# Fixed parallelism for class-level concurrency
junit.jupiter.execution.parallel.config.strategy = fixed
junit.jupiter.execution.parallel.config.fixed.parallelism = 4
# Execute test methods in the order specified by @Order annotations
junit.jupiter.testmethod.order.default = org.junit.jupiter.api.MethodOrderer$OrderAnnotation

View File

@ -4,7 +4,7 @@
"dataSets": { "dataSets": {
"dataSet-0": { "dataSet-0": {
"username": "utest@utopiadeals.com", "username": "utest@utopiadeals.com",
"password": "utest0001" "password": "utest001"
}, },
"dataSet-1": { "dataSet-1": {
"username": "abdullah@utopiadeals.com", "username": "abdullah@utopiadeals.com",