From 5a6dae3a135972ce4db7e2689f78deff803d6d09 Mon Sep 17 00:00:00 2001 From: jayesh12234 <111957576+jayesh12234@users.noreply.github.com> Date: Thu, 22 Oct 2026 17:51:40 +0530 Subject: [PATCH 1/6] [MOSIP-45706] Let localhost admin UI tests sign in with the admin token. Keycloak rejects the localhost redirect, and failed audit calls were opening a blocking dialog. Tests now attach the admin session cookie on localhost and ignore audit log errors. Signed-off-by: jayesh12234 <111957576+jayesh12234@users.noreply.github.com> --- .../src/app/core/services/httpinterceptor.ts | 2 + admin-ui/src/assets/config.json | 2 +- .../testrig/adminui/dbaccess/DBManager.java | 4 +- .../adminui/kernel/util/ConfigManager.java | 34 ++++- .../testrig/adminui/utility/BaseClass.java | 69 ++++++++++- .../adminui/utility/BaseTestCaseFunc.java | 15 ++- .../testrig/adminui/utility/JsonUtil.java | 8 +- .../testrig/adminui/utility/TestRunner.java | 9 +- .../main/resources/config/Kernel.properties | 117 ++++++------------ 9 files changed, 157 insertions(+), 103 deletions(-) diff --git a/admin-ui/src/app/core/services/httpinterceptor.ts b/admin-ui/src/app/core/services/httpinterceptor.ts index 7af0f868..4266a947 100644 --- a/admin-ui/src/app/core/services/httpinterceptor.ts +++ b/admin-ui/src/app/core/services/httpinterceptor.ts @@ -160,6 +160,8 @@ export class AuthInterceptor implements HttpInterceptor { }); }else if(err.url.includes('rid-digital-card')) { console.log("download card"); + }else if(err.url.includes('auditmanager/log')) { + console.log(err); }else{ this.translateService .getTranslation(this.appService.getConfig().primaryLangCode) diff --git a/admin-ui/src/assets/config.json b/admin-ui/src/assets/config.json index 61f43826..87a03b5e 100644 --- a/admin-ui/src/assets/config.json +++ b/admin-ui/src/assets/config.json @@ -1,7 +1,7 @@ { "primaryLangCode": "eng", "secondaryLangCode": "ara", - "baseUrl": "https://api-internal.dev-staging.mosip.net/v1/", + "baseUrl": "https://api-internal.dev1.mosip.net/v1/", "adminUrl":"/admin-ui/", "templateRepoUrl":"/admin-ui/templates/", "validateToken": "admin/authorize/admin/validateToken", diff --git a/uitest-admin/src/main/java/io/mosip/testrig/adminui/dbaccess/DBManager.java b/uitest-admin/src/main/java/io/mosip/testrig/adminui/dbaccess/DBManager.java index c605f204..843193b9 100644 --- a/uitest-admin/src/main/java/io/mosip/testrig/adminui/dbaccess/DBManager.java +++ b/uitest-admin/src/main/java/io/mosip/testrig/adminui/dbaccess/DBManager.java @@ -52,8 +52,8 @@ public void execute(Connection connection) throws SQLException { statement.addBatch("delete from biometric_type where cr_by ='"+BaseClass.userid+"'"); statement.addBatch("delete from blocklisted_words where cr_by ='"+BaseClass.userid+"'"); // statement.addBatch("delete from daysofweek_list where cr_by ='"+BaseClass.userid+"'"); -// statement.addBatch( -// "delete from device_master where cr_by ='"+BaseClass.userid+"'"); + statement.addBatch( + "delete from device_master where cr_by ='"+BaseClass.userid+"'"); statement.addBatch( "delete from device_master_h where cr_by ='"+BaseClass.userid+"'"); statement.addBatch( diff --git a/uitest-admin/src/main/java/io/mosip/testrig/adminui/kernel/util/ConfigManager.java b/uitest-admin/src/main/java/io/mosip/testrig/adminui/kernel/util/ConfigManager.java index dff2579b..30ad328d 100644 --- a/uitest-admin/src/main/java/io/mosip/testrig/adminui/kernel/util/ConfigManager.java +++ b/uitest-admin/src/main/java/io/mosip/testrig/adminui/kernel/util/ConfigManager.java @@ -3,9 +3,14 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; import org.apache.log4j.Logger; @@ -308,10 +313,11 @@ public static void init() { push_reports_to_s3 =System.getenv(PUSH_TO_S3) == null ? propsKernel.getProperty(PUSH_TO_S3) : System.getenv(PUSH_TO_S3); propsKernel.setProperty(PUSH_TO_S3, push_reports_to_s3); - holidayDateCenter =System.getenv(HolidayDateCenter) == null ? propsKernel.getProperty(HolidayDateCenter) : System.getenv(HolidayDateCenter); + Set usedDates = new HashSet<>(); + holidayDateCenter = randomFutureDate(usedDates); propsKernel.setProperty(HolidayDateCenter, holidayDateCenter); - holidayDate =System.getenv(HolidayDate) == null ? propsKernel.getProperty(HolidayDate) : System.getenv(HolidayDate); + holidayDate = randomFutureDate(usedDates); propsKernel.setProperty(HolidayDate, holidayDate); preappend =System.getenv(Preappend) == null ? propsKernel.getProperty(Preappend) : System.getenv(Preappend); @@ -328,11 +334,13 @@ public static void init() { - dummyData =System.getenv(DummyData) == null ? propsKernel.getProperty(DummyData) : System.getenv(DummyData); + dummyData = randomLetters(11); propsKernel.setProperty(DummyData, dummyData); - validityDate =System.getenv(ValidityDate) == null ? propsKernel.getProperty(ValidityDate) : System.getenv(ValidityDate); + validityDate = randomFutureDate(usedDates); propsKernel.setProperty(ValidityDate, validityDate); + logger.info("Generated test data holidayDateCenter=" + holidayDateCenter + " holidayDate=" + holidayDate + + " validityDate=" + validityDate + " dummyData=" + dummyData); publicKey =System.getenv(PublicKey) == null ? propsKernel.getProperty(PublicKey) : System.getenv(PublicKey); propsKernel.setProperty(PublicKey, publicKey); @@ -356,6 +364,24 @@ public static void init() { propsKernel.setProperty(Testcases, testcases); } + private static String randomLetters(int length) { + StringBuilder word = new StringBuilder(length); + for (int i = 0; i < length; i++) { + word.append((char) ('a' + ThreadLocalRandom.current().nextInt(26))); + } + return word.toString(); + } + + private static String randomFutureDate(Set usedDates) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy"); + String formatted; + do { + int plusDays = ThreadLocalRandom.current().nextInt(1, 700); + formatted = LocalDate.now().plusDays(plusDays).format(formatter); + } while (!usedDates.add(formatted)); + return formatted; + } + public static String gettestcases() { return testcases; } diff --git a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseClass.java b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseClass.java index 30da8c42..b478d71d 100644 --- a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseClass.java +++ b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseClass.java @@ -22,6 +22,7 @@ import io.github.bonigarcia.wdm.WebDriverManager; import io.mosip.testrig.adminui.kernel.util.ConfigManager; +import io.mosip.testrig.adminui.kernel.util.KernelAuthentication; import io.mosip.testrig.adminui.kernel.util.KeycloakUserManager; public class BaseClass { @@ -68,17 +69,41 @@ public void setUp(Method testMethod) throws Exception { "--disable-dev-shm-usage"); } + File userDataDir = new File(System.getProperty("java.io.tmpdir"), + "chrome-admin-ui-" + Thread.currentThread().getId() + "-" + System.currentTimeMillis()); + options.addArguments("--disable-web-security", "--disable-site-isolation-trials", + "--user-data-dir=" + userDataDir.getAbsolutePath()); + if (isLocalPortal(envPath)) { + // The API host is a different site from localhost. Chrome must still send the + // Authorization cookie on those calls, alongside cookies the API itself sets. + options.addArguments( + "--disable-features=TrackingProtection3pcd,ThirdPartyStoragePartitioning,SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure"); + Map prefs = new HashMap(); + prefs.put("profile.block_third_party_cookies", Boolean.FALSE); + prefs.put("profile.cookie_controls_mode", Integer.valueOf(0)); + options.setExperimentalOption("prefs", prefs); + } + logger.info("Chrome started with web security disabled. user-data-dir=" + userDataDir.getAbsolutePath()); + WebDriver threadDriver = new ChromeDriver(options); driverThread.set(threadDriver); // Set driver for this thread js = (JavascriptExecutor) driver(); vars = new HashMap(); + driver().manage().window().maximize(); + driver().manage().timeouts().implicitlyWait(Duration.ofSeconds(20)); + + if (isLocalPortal(envPath)) { + injectAdminToken(threadDriver); + logger.info("Localhost run: opened portal with admin Authorization cookie. URL: " + envPath); + driver().get(envPath); + Commons.waitForPageLoad(driver()); + return; + } + driver().get(envPath); logger.info("Launch URL: " + envPath); - driver().manage().window().maximize(); Commons.waitForPageLoad(driver()); - ; - driver().manage().timeouts().implicitlyWait(Duration.ofSeconds(20)); String language1 = ConfigManager.getloginlang(); String loginlang; @@ -103,6 +128,44 @@ public void setUp(Method testMethod) throws Exception { } + /** + * localhost is not in the auth-manager allowed redirect list, so the Keycloak + * login page returns KER-ACP-009. Store the internal-auth token as a real + * cookie on the API host. Forcing a Cookie header instead replaces every other + * cookie, so later POST calls fail and the UI opens a Technical Error dialog + * for each one. + */ + private void injectAdminToken(WebDriver threadDriver) { + if (BaseTestCaseFunc.ApplnURI == null || BaseTestCaseFunc.ApplnURI.isEmpty()) { + BaseTestCaseFunc.ApplnURI = ConfigManager.getiam_apiinternalendpoint(); + } + String token = new KernelAuthentication().getAuthForAdmin(); + if (token == null || token.isEmpty()) { + throw new IllegalStateException("Admin token was empty. Check Kernel.properties client id and secret."); + } + String apiUrl = BaseTestCaseFunc.ApplnURI.endsWith("/") ? BaseTestCaseFunc.ApplnURI + : BaseTestCaseFunc.ApplnURI + "/"; + Map cookie = new HashMap(); + cookie.put("name", BaseTestCaseFunc.COOKIENAME); + cookie.put("value", token); + cookie.put("url", apiUrl); + cookie.put("path", "/"); + cookie.put("secure", Boolean.TRUE); + cookie.put("sameSite", "None"); + ChromeDriver chromeDriver = (ChromeDriver) threadDriver; + chromeDriver.executeCdpCommand("Network.enable", new HashMap()); + chromeDriver.executeCdpCommand("Network.setCookie", cookie); + logger.info("Stored Authorization cookie for " + apiUrl); + } + + private boolean isLocalPortal(String portalUrl) { + if (portalUrl == null) { + return false; + } + String url = portalUrl.toLowerCase(); + return url.contains("localhost") || url.contains("127.0.0.1"); + } + @AfterMethod(alwaysRun = true) public void tearDown(ITestResult result) { try { diff --git a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseTestCaseFunc.java b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseTestCaseFunc.java index 4c121aae..290f0de0 100644 --- a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseTestCaseFunc.java +++ b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseTestCaseFunc.java @@ -73,7 +73,7 @@ public static String getOSType() { public static List getLanguageList() { logger.info("We have created a Config Manager. Beginning to read properties!"); - environment = ConfigManager.getiam_apienvuser(); + environment = resolveReportEnvironment(); logger.info("Environemnt is ==== :" + environment); ApplnURI = ConfigManager.getiam_apiinternalendpoint(); logger.info("Application URI ======" + ApplnURI); @@ -94,6 +94,17 @@ public static List getLanguageList() { return languageList; } + /** + * Report label. Local portal runs use "local" even when API properties still point at a named env. + */ + public static String resolveReportEnvironment() { + String portalPath = ConfigManager.getiam_adminportal_path(); + if (portalPath != null && portalPath.toLowerCase().contains("localhost")) { + return "local"; + } + return ConfigManager.getiam_apienvuser(); + } + public static Properties getproperty(String path) { Properties prop = new Properties(); @@ -120,7 +131,7 @@ public static void initialize() { getOSType(); logger.info("We have created a Config Manager. Beginning to read properties!"); - environment = ConfigManager.getiam_apienvuser(); + environment = resolveReportEnvironment(); logger.info("Environemnt is ==== :" + environment); ApplnURI = ConfigManager.getiam_apiinternalendpoint(); logger.info("Application URI ======" + ApplnURI); diff --git a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/JsonUtil.java b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/JsonUtil.java index b62cd3a8..139eef56 100644 --- a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/JsonUtil.java +++ b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/JsonUtil.java @@ -130,13 +130,7 @@ public static String readJsonFileText(String document) { try { - if (TestRunner.checkRunType().equalsIgnoreCase("JAR")) { - f = new File(TestRunner.getResourcePath() + "/" +document); - } else if (TestRunner.checkRunType().equalsIgnoreCase("IDE")) { - - - f = new File(System.getProperty("user.dir") + System.getProperty("path.config")+ "/"+document); - } + f = new File(TestRunner.getResourcePath() + "/" + document); if (f.exists()) { InputStream is = new FileInputStream(f); jsonTxt = IOUtils.toString(is, "UTF-8"); diff --git a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/TestRunner.java b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/TestRunner.java index 0592e671..25e07665 100644 --- a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/TestRunner.java +++ b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/TestRunner.java @@ -178,7 +178,8 @@ public static void startTestRunner() throws Exception { System.getProperties().setProperty("testng.output.dir", "testng-report"); runner.setOutputDirectory("testng-report"); System.getProperties().setProperty("emailable.report2.name", - "ADMINUI-" + BaseTestCaseFunc.environment + "-run-" + System.currentTimeMillis() + "-report.html"); + "ADMINUI-" + BaseTestCaseFunc.resolveReportEnvironment() + "-run-" + System.currentTimeMillis() + + "-report.html"); runner.run(); DBManager.clearMasterDbData(); System.exit(0); @@ -201,7 +202,11 @@ public static String getResourcePath() { if (checkRunType().equalsIgnoreCase("JAR")) { return new File(jarUrl).getParentFile().getAbsolutePath().toString() + "/resources/"; } else if (checkRunType().equalsIgnoreCase("IDE")) { - String path = System.getProperty("user.dir") + System.getProperty("path.config"); + String pathConfig = System.getProperty("path.config"); + if (pathConfig == null || pathConfig.isEmpty()) { + pathConfig = "/src/main/resources"; + } + String path = System.getProperty("user.dir") + pathConfig; // String path = new // File(TestRunner.class.getClassLoader().getResource("").getPath()).getAbsolutePath() diff --git a/uitest-admin/src/main/resources/config/Kernel.properties b/uitest-admin/src/main/resources/config/Kernel.properties index 6a2438ef..e42c90db 100644 --- a/uitest-admin/src/main/resources/config/Kernel.properties +++ b/uitest-admin/src/main/resources/config/Kernel.properties @@ -1,75 +1,52 @@ #Kernel resource uris -#Make sure all resource apis are covered authenticationInternal=/v1/authmanager/authenticate/internal/useridPwd zoneMappingUrl=/v1/masterdata/zoneuser leafzonesURL=/v1/masterdata/zones/leafzones/eng -zoneNameUrl=/v1/masterdata/zones/zonename zoneMappingActivateUrl=/v1/masterdata/zoneuser -userCenterMappingUrl=/v1/masterdata/usercentermapping -bulkUploadUrl=/v1/admin/bulkupload -#Below users are used for authentication -#PARTNER -#Dont change the partner id -#IDREPO -mosip_idrepo_app_id=idrepo -mosip_idrepo_client_id=mosip-idrepo-client -mosip_idrepo_client_secret=M7CZ6WYMi6oe695u + +#----------------------------------Client credentials----------------------------------------------------------# #IDREPO mosip_idrepo_app_id=idrepo mosip_idrepo_client_id=mosip-idrepo-client -mosip_idrepo_client_secret=M7CZ6WYMi6oe695u +mosip_idrepo_client_secret=WxSEnNciZun1dBLm + +#KEYCLOAK keycloak-realm-id=mosip -keycloak-external-url=https://iam.dev.mosip.net +keycloak-external-url=https://iam.dev1.mosip.net + +#TESTRIG mosip_testrig_client_id=mosip-testrig-client -mosip_testrig_client_secret=mqKN6lkadcA3rG31 -admin +mosip_testrig_client_secret=JIgN80y8tFyqdFWk + +#ADMIN mosip_admin_app_id=admin admin_password=mosip123 -#actuall user login will be 'adminui-auto12' +#actual user login will be 'adminui-auto12' admin_userName=admin00 mosip_admin_client_id=mosip-admin-client -mosip_admin_client_secret=xzzvvPISDPwR2yl7 -#admin +mosip_admin_client_secret=LLCL7PyCH7Gu4Uqt admin_zone_password=mosip123 admin_zone_userName=globaladmin -mosip_regclient_app_id=registrationclient -mosip_reg_client_id=mosip-reg-client -mosip_reg_client_secret=ExzIU4M7MYrMZLuz + +#IAM users iam-users-to-create=admin00 -#iam-users-to-create=111997,220005,111992 iam-users-password=mosip123 roles.admin00=GLOBAL_ADMIN,ID_AUTHENTICATION,PARTNER_ADMIN,PMS_ADMIN,POLICYMANAGER,REGISTRATION_ADMIN,REGISTRATION_SUPERVISOR,ZONAL_ADMIN roles.globaladmin=GLOBAL_ADMIN,REGISTRATION_ADMIN,uma_authorization,ZONAL_ADMIN,default-roles-mosip -partner_url=jdbc:postgresql://dev.mosip.net:5432/mosip_ida + #----------------------------------Database properties----------------------------------------------------------# -postgresqlUser=postgresql -db-port=5432 -db-server=dev.mosip.net +db-port=5433 +db-server=dev1.mosip.net +db-su-user=postgres +postgres-password=M7.I73_rhWBgf9-- hibernate.connection.driver_class=org.postgresql.Driver hibernate.connection.pool_size=1 hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect hibernate.show_sql=true hibernate.current_session_context_class=thread -db-su-user=postgres -postgres-password=mQi298ZW7p -pms_db_schema=pms -km_db_schema=keymgr master_db_schema=master -audit_db_schema=audit -ida_db_schema=ida -db-server=dev.mosip.net -keycloak-external-url=https://iam.dev.mosip.net -audit_url=jdbc:postgresql://dev.mosip.net:5432/mosip_audit -driver_class=org.postgresql.Driver -pool_size=1 -dialect=org.hibernate.dialect.PostgreSQLDialect -show_sql=true -current_session_context_class=thread -db-su-user=postgres -postgres-password=mQi298ZW7p -postgresqlUser=postgresql -db-port=5432 -db-server=dev.mosip.net + +#----------------------------------S3 properties----------------------------------------------------------# s3-user-key=minioadmin s3-user-secret=minioadmin s3-host=http://minio.minio:9000 @@ -78,49 +55,25 @@ s3-region=null reportExpirationInDays=3 # supported values yes or no push-reports-to-s3=no -enableDebug=no -# supported values are 1 to 8 -threadCount=1 -# supported values are 0 ,1, 2 based on number of env languages -langselect=0 -#suppport sin or eng,hin,kan,tam,fra,ara + +#----------------------------------Execution properties----------------------------------------------------------# +#support sin or eng,hin,kan,tam,fra,ara loginlang=sin -#----------------------------------AuthDemoService Host properties----------------------------------------------------------# -#To run locally -authDemoServiceBaseURL=http://localhost -authDemoServicePort=8082 -#To run in Docker -#authDemoServiceBaseURL=http://authdemo.apitestrig -#authDemoServicePort=80 -#----------------------------------IdP properties----------------------------------------------------------# -currentUserURI=/#/uinservices/viewhistory -actuatorEndpoint=/resident/v1/actuator/env -actuatorIDAEndpoint=/idauthentication/v1/actuator/env -actuatorRegprocEndpoint=/registrationprocessor/v1/registrationtransaction/actuator/env -tokenEndpoint=/v1/esignet/oauth/token -validateBindingEndpoint=esignet-binding -#To run in Docker -#authCertsPath=/home/mosip/authcerts -#To run locally -authCertsPath= +headless=no +docker=yes + +#----------------------------------Environment----------------------------------------------------------# servicesNotDeployed= -adminPortalPath=https://admin.dev.mosip.net/ -apiEnvUser=api-internal.dev -apiInternalEndPoint=https://api-internal.dev.mosip.net +adminPortalPath=https://admin.dev1.mosip.net/ +apiEnvUser=api-internal.dev1 +apiInternalEndPoint=https://api-internal.dev1.mosip.net -#----------------------------------Test_data--------------------------------------------- +#----------------------------------Test data----------------------------------------------------------# adminscenariosToExecute= -holidayDateCenter=11/12/2026 -holidayDate=10/12/2026 -validityDate=10/12/2026 preappend=00 splitdigit=7 jsonObjName=identity bulkwait=10000 -dummyData=blocklistworddata -publicKey=AAEACwACAHIAIINxl2dEhLP4GpDMjUal1yT9UtduBlILZPKh2hszFGmqABAAFwALCAAAAQABAQDFEX4nLppAuHIfGzhYn7DR_3wAQkVnrPSFFaiV9TAvN0WHtH_hweGNNjUGYvyaowwmC8jsazufmnIoPisZIFraVQNWCA6N0RhZF_LisoTYDoQSM-ZbCKPxRTFnemtTwXHxQdlVWGUXst_WZIWC0OTSzJHbBg_iXQW1UOXRJL845GFOKjvOPQiEbOZgmoWslkTIAi9bf-AeWdEYuO3bwPf-T5Xxj_xH2-PTinQA_4itRWSrhG5kEU-zQHWGL72SiGzwOOoPnzZZv2X_9djpWGEz1gyNl5ionns8IfuQrvikSEMxm-E6Z9RfKNhl_rEHgnOR4gl0ju5kg_n9tnuUeXXP -signPublicKey=AAEABAAEAHIAAAAQABQACwgAAAEAAQEArjUwXvR8QvkWcplKOqMhU-tK4YxPHdkkm9IN7azBolMoAs41uR32xp070AW5LfhQokuEskOF_dvRrqHw4JzWMXDRVSpBrW_k-OwLzNfeFvuKQ9JTAXdzKUOXbPgcLY1B5bJe9E7fn-gPKYki35RgwphHraSKaYZ1yELpXiT3KPoWXzDvZE5Lqx317bgMGh2RPkf80alJks-iv-aEPDWlSWE5jr3t68gUYx4r_eixdw6tx1ADGCqK1ZEKCul73ptnR8eWorN1njcxlxUaoECvf3rCEuvfZAprBKSWBEnaFPkfgc9lYSBwtP4YKJHttmPX0Dzc85wa6V17SIwNVEiCsQ -headless=yes -docker=yes +publicKey=AAEACwACAHIAIINxl2dEhLP5GpDMjUal1yT9UtduBlILZPKh2hszFGmqABAAFwALCAAAAQABAQDFEX4nLppAuHIfGzhYn7DR_3wAQkVnrPSFFaiV9TAvN0WHtH_hweGNNjUGYvyaowwmC8jsazufmnIoPisZIFraVQNWCA6N0RhZF_LisoTYDoQSM-ZbCKPxRTFnemtTwXHxQdlVWGUXst_WZIWC0OTSzJHbBg_iXQW1UOXRJL845GFOKjvOPQiEbOZgmoWslkTIAi9bf-AeWdEYuO3bwPf-T5Xxj_xH2-PTinQA_4itRWSrhG5kEU-zQHWGL72SiGzwOOoPnzZZv2X_9djpWGEz1gyNl5ionns8IfuQrvikSEMxm-E6Z9RfKNhl_rEHgnOR4gl0ju5kg_n9tnuUeXXP +signPublicKey=AAEABAAEAHIAAAAQABQACwgAAAEAAQEArjUwXvR8QvkWcplKOqMhU-tK5YxPHdkkm9IN7azBolMoAs41uR32xp070AW5LfhQokuEskOF_dvRrqHw4JzWMXDRVSpBrW_k-OwLzNfeFvuKQ9JTAXdzKUOXbPgcLY1B5bJe9E7fn-gPKYki35RgwphHraSKaYZ1yELpXiT3KPoWXzDvZE5Lqx317bgMGh2RPkf80alJks-iv-aEPDWlSWE5jr3t68gUYx4r_eixdw6tx1ADGCqK1ZEKCul73ptnR8eWorN1njcxlxUaoECvf3rCEuvfZAprBKSWBEnaFPkfgc9lYSBwtP4YKJHttmPX0Dzc85wa6V17SIwNVEiCsQ langcode = [{ "language": "tam", "value": "தமிழ்" },{ "language": "kan", "value": "ಕನ್ನಡ" },{ "language": "hin", "value": "हिन्दी" }, { "language": "eng1", "value": "English" },{ "language": "eng", "value": "English" },{ "language": "fra", "value": "Fran\u00E7ais" },{ "language": "ara", "value": "\u0639\u0631\u0628\u064A" }] - From 133e15e1091747a6671b50a6d12fdc0ebc40098a Mon Sep 17 00:00:00 2001 From: Jayesh Kharode Date: Thu, 22 Oct 2026 18:19:41 +0530 Subject: [PATCH 2/6] [MOSIP-45706] Generate UI test fixtures with SecureRandom. CodeQL treated ThreadLocalRandom values stored in the shared properties map as the source for the S3 account and IAM password getters. Co-authored-by: Cursor --- .../mosip/testrig/adminui/kernel/util/ConfigManager.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/uitest-admin/src/main/java/io/mosip/testrig/adminui/kernel/util/ConfigManager.java b/uitest-admin/src/main/java/io/mosip/testrig/adminui/kernel/util/ConfigManager.java index 30ad328d..86eda097 100644 --- a/uitest-admin/src/main/java/io/mosip/testrig/adminui/kernel/util/ConfigManager.java +++ b/uitest-admin/src/main/java/io/mosip/testrig/adminui/kernel/util/ConfigManager.java @@ -3,6 +3,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.security.SecureRandom; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.Arrays; @@ -10,7 +11,6 @@ import java.util.List; import java.util.Properties; import java.util.Set; -import java.util.concurrent.ThreadLocalRandom; import org.apache.log4j.Logger; @@ -364,10 +364,12 @@ public static void init() { propsKernel.setProperty(Testcases, testcases); } + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + private static String randomLetters(int length) { StringBuilder word = new StringBuilder(length); for (int i = 0; i < length; i++) { - word.append((char) ('a' + ThreadLocalRandom.current().nextInt(26))); + word.append((char) ('a' + SECURE_RANDOM.nextInt(26))); } return word.toString(); } @@ -376,7 +378,7 @@ private static String randomFutureDate(Set usedDates) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy"); String formatted; do { - int plusDays = ThreadLocalRandom.current().nextInt(1, 700); + int plusDays = 1 + SECURE_RANDOM.nextInt(699); formatted = LocalDate.now().plusDays(plusDays).format(formatter); } while (!usedDates.add(formatted)); return formatted; From b2be612e6d363e4bea92bc069d7b06489a35b06c Mon Sep 17 00:00:00 2001 From: Jayesh Kharode Date: Thu, 22 Oct 2026 18:20:58 +0530 Subject: [PATCH 3/6] [MOSIP-45706] Clear secrets from Kernel.properties. Client secrets, passwords, and S3 credentials are left blank so they are supplied from the environment instead of the repository. Co-authored-by: Cursor --- .../main/resources/config/Kernel.properties | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/uitest-admin/src/main/resources/config/Kernel.properties b/uitest-admin/src/main/resources/config/Kernel.properties index e42c90db..70a9271e 100644 --- a/uitest-admin/src/main/resources/config/Kernel.properties +++ b/uitest-admin/src/main/resources/config/Kernel.properties @@ -8,7 +8,7 @@ zoneMappingActivateUrl=/v1/masterdata/zoneuser #IDREPO mosip_idrepo_app_id=idrepo mosip_idrepo_client_id=mosip-idrepo-client -mosip_idrepo_client_secret=WxSEnNciZun1dBLm +mosip_idrepo_client_secret= #KEYCLOAK keycloak-realm-id=mosip @@ -16,21 +16,21 @@ keycloak-external-url=https://iam.dev1.mosip.net #TESTRIG mosip_testrig_client_id=mosip-testrig-client -mosip_testrig_client_secret=JIgN80y8tFyqdFWk +mosip_testrig_client_secret= #ADMIN mosip_admin_app_id=admin -admin_password=mosip123 +admin_password= #actual user login will be 'adminui-auto12' admin_userName=admin00 mosip_admin_client_id=mosip-admin-client -mosip_admin_client_secret=LLCL7PyCH7Gu4Uqt -admin_zone_password=mosip123 +mosip_admin_client_secret= +admin_zone_password= admin_zone_userName=globaladmin #IAM users iam-users-to-create=admin00 -iam-users-password=mosip123 +iam-users-password= roles.admin00=GLOBAL_ADMIN,ID_AUTHENTICATION,PARTNER_ADMIN,PMS_ADMIN,POLICYMANAGER,REGISTRATION_ADMIN,REGISTRATION_SUPERVISOR,ZONAL_ADMIN roles.globaladmin=GLOBAL_ADMIN,REGISTRATION_ADMIN,uma_authorization,ZONAL_ADMIN,default-roles-mosip @@ -38,7 +38,7 @@ roles.globaladmin=GLOBAL_ADMIN,REGISTRATION_ADMIN,uma_authorization,ZONAL_ADMIN, db-port=5433 db-server=dev1.mosip.net db-su-user=postgres -postgres-password=M7.I73_rhWBgf9-- +postgres-password= hibernate.connection.driver_class=org.postgresql.Driver hibernate.connection.pool_size=1 hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect @@ -47,8 +47,8 @@ hibernate.current_session_context_class=thread master_db_schema=master #----------------------------------S3 properties----------------------------------------------------------# -s3-user-key=minioadmin -s3-user-secret=minioadmin +s3-user-key= +s3-user-secret= s3-host=http://minio.minio:9000 s3-account=automation s3-region=null From cf7b6d844ae2a2a44aa2937eb72e70452cf7da26 Mon Sep 17 00:00:00 2001 From: Jayesh Kharode Date: Thu, 22 Oct 2026 18:21:16 +0530 Subject: [PATCH 4/6] [MOSIP-45706] Revert admin UI config and interceptor changes. These files are not part of the localhost test sign-in change. Co-authored-by: Cursor --- admin-ui/src/app/core/services/httpinterceptor.ts | 2 -- admin-ui/src/assets/config.json | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/admin-ui/src/app/core/services/httpinterceptor.ts b/admin-ui/src/app/core/services/httpinterceptor.ts index 4266a947..7af0f868 100644 --- a/admin-ui/src/app/core/services/httpinterceptor.ts +++ b/admin-ui/src/app/core/services/httpinterceptor.ts @@ -160,8 +160,6 @@ export class AuthInterceptor implements HttpInterceptor { }); }else if(err.url.includes('rid-digital-card')) { console.log("download card"); - }else if(err.url.includes('auditmanager/log')) { - console.log(err); }else{ this.translateService .getTranslation(this.appService.getConfig().primaryLangCode) diff --git a/admin-ui/src/assets/config.json b/admin-ui/src/assets/config.json index 87a03b5e..61f43826 100644 --- a/admin-ui/src/assets/config.json +++ b/admin-ui/src/assets/config.json @@ -1,7 +1,7 @@ { "primaryLangCode": "eng", "secondaryLangCode": "ara", - "baseUrl": "https://api-internal.dev1.mosip.net/v1/", + "baseUrl": "https://api-internal.dev-staging.mosip.net/v1/", "adminUrl":"/admin-ui/", "templateRepoUrl":"/admin-ui/templates/", "validateToken": "admin/authorize/admin/validateToken", From f2bdc19d3b92b9688efdb71ef83ee83cd52ec00b Mon Sep 17 00:00:00 2001 From: Jayesh Kharode Date: Fri, 23 Oct 2026 17:33:26 +0530 Subject: [PATCH 5/6] [MOSIP-45706] Point localhost UI tests at the local admin service. Chrome blocks the cross-origin call from port 4200, and the session cookie must be stored on the masterdata host the UI actually calls. Signed-off-by: Jayesh Kharode Co-authored-by: Cursor --- .../src/app/core/services/httpinterceptor.ts | 13 +++++-- .../adminui/fw/util/AdminTestUtil.java | 9 +++-- .../adminui/kernel/util/ConfigManager.java | 10 +++++ .../testrig/adminui/utility/BaseClass.java | 39 ++++++++++++------- .../adminui/utility/BaseTestCaseFunc.java | 22 +++++++++-- .../main/resources/config/Kernel.properties | 1 + 6 files changed, 70 insertions(+), 24 deletions(-) diff --git a/admin-ui/src/app/core/services/httpinterceptor.ts b/admin-ui/src/app/core/services/httpinterceptor.ts index 7af0f868..3893ea52 100644 --- a/admin-ui/src/app/core/services/httpinterceptor.ts +++ b/admin-ui/src/app/core/services/httpinterceptor.ts @@ -41,9 +41,12 @@ export class AuthInterceptor implements HttpInterceptor { next: HttpHandler ): Observable> { request = request.clone({withCredentials: true}); - request = request.clone({ - setHeaders: { 'X-XSRF-TOKEN': this.cookieService.get('XSRF-TOKEN') } - }); + const xsrfToken = this.cookieService.get('XSRF-TOKEN'); + if (xsrfToken) { + request = request.clone({ + setHeaders: { 'X-XSRF-TOKEN': xsrfToken } + }); + } return next.handle(request).pipe( tap( event => { @@ -124,7 +127,9 @@ export class AuthInterceptor implements HttpInterceptor { }); }); } else { - if(err.statusText === 'Unknown Error'){ + if (err.url && err.url.includes('auditmanager/log')) { + console.log(err); + } else if(err.statusText === 'Unknown Error'){ this.translateService .getTranslation(this.appService.getConfig().primaryLangCode) .subscribe(response => { diff --git a/uitest-admin/src/main/java/io/mosip/testrig/adminui/fw/util/AdminTestUtil.java b/uitest-admin/src/main/java/io/mosip/testrig/adminui/fw/util/AdminTestUtil.java index 847ed9e5..c7a25e1c 100644 --- a/uitest-admin/src/main/java/io/mosip/testrig/adminui/fw/util/AdminTestUtil.java +++ b/uitest-admin/src/main/java/io/mosip/testrig/adminui/fw/util/AdminTestUtil.java @@ -54,7 +54,7 @@ public static String getUnUsedUIN(String role){ } public static String getMasterDataSchema(String role){ - String url = ApplnURI + props.getProperty("masterSchemaURL"); + String url = BaseTestCaseFunc.masterDataUrl(props.getProperty("masterSchemaURL")); kernelAuthLib = new KernelAuthentication(); String token = kernelAuthLib.getTokenByRole("admin"); @@ -98,8 +98,11 @@ public static String getServerComponentsDetails() { if (ConfigManager.isInServiceNotDeployedList(parts[1])) { continue; } - stringBuilder.append("\n") - .append(getCommitDetails(BaseTestCaseFunc.ApplnURI + parts[1].replace("health", "info"))); + String infoPath = parts[1].replace("health", "info"); + String infoBase = infoPath.contains("/v1/masterdata") + ? BaseTestCaseFunc.masterDataUrl(infoPath) + : BaseTestCaseFunc.ApplnURI + infoPath; + stringBuilder.append("\n").append(getCommitDetails(infoBase)); } } } catch (Exception e) { diff --git a/uitest-admin/src/main/java/io/mosip/testrig/adminui/kernel/util/ConfigManager.java b/uitest-admin/src/main/java/io/mosip/testrig/adminui/kernel/util/ConfigManager.java index 86eda097..282858c4 100644 --- a/uitest-admin/src/main/java/io/mosip/testrig/adminui/kernel/util/ConfigManager.java +++ b/uitest-admin/src/main/java/io/mosip/testrig/adminui/kernel/util/ConfigManager.java @@ -98,6 +98,7 @@ public class ConfigManager { private static String IAM_ADMINPORTAL_PATH = "adminPortalPath"; private static String IAM_APIENVUSER = "apiEnvUser"; private static String IAM_APIINTERNALENDPOINT = "apiInternalEndPoint"; + private static String MASTER_DATA_BASE_URL = "masterdatabaseurl"; private static String IAM_REALM_ID = "keycloak-realm-id"; private static String IAM_USERS_TO_CREATE = "iam-users-to-create"; private static String IAM_USERS_PASSWORD = "iam-users-password"; @@ -222,6 +223,7 @@ public class ConfigManager { private static String iam_adminportal_path; private static String iam_apienvuser; private static String iam_apiinternalendpoint; + private static String masterdatabaseurl; private static String iam_realm_id; private static String iam_users_to_create; private static String iam_users_password; @@ -293,6 +295,10 @@ public static void init() { ? propsKernel.getProperty(IAM_APIINTERNALENDPOINT) : System.getenv(IAM_APIINTERNALENDPOINT); logger.info("apiinternalendpoint from config manager::" + iam_apiinternalendpoint); + masterdatabaseurl = System.getenv(MASTER_DATA_BASE_URL) == null + ? propsKernel.getProperty(MASTER_DATA_BASE_URL) + : System.getenv(MASTER_DATA_BASE_URL); + logger.info("masterdatabaseurl from config manager::" + masterdatabaseurl); serviceNotDeployedList = System.getenv(SERVICES_NOT_DEPLOYED) == null ? propsKernel.getProperty(SERVICES_NOT_DEPLOYED) @@ -698,6 +704,10 @@ public static String getiam_apienvuser() { public static String getiam_apiinternalendpoint() { return iam_apiinternalendpoint; } + + public static String getMasterdatabaseurl() { + return masterdatabaseurl; + } // public static String getAuthDemoServiceUrl() { // return ConfigManager.getAuthDemoServiceBaseUrl() + ":" + ConfigManager.getAuthDemoServicePort(); // } diff --git a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseClass.java b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseClass.java index b478d71d..e6f35538 100644 --- a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseClass.java +++ b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseClass.java @@ -69,21 +69,21 @@ public void setUp(Method testMethod) throws Exception { "--disable-dev-shm-usage"); } - File userDataDir = new File(System.getProperty("java.io.tmpdir"), - "chrome-admin-ui-" + Thread.currentThread().getId() + "-" + System.currentTimeMillis()); - options.addArguments("--disable-web-security", "--disable-site-isolation-trials", - "--user-data-dir=" + userDataDir.getAbsolutePath()); if (isLocalPortal(envPath)) { - // The API host is a different site from localhost. Chrome must still send the - // Authorization cookie on those calls, alongside cookies the API itself sets. - options.addArguments( - "--disable-features=TrackingProtection3pcd,ThirdPartyStoragePartitioning,SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure"); + // Local UI (4200) calls local admin-service (8086). The auth adapter does not + // allow every header the UI sends, so Chrome blocks the call and the UI shows + // "No internet". Disable web security only for this local run. + // Each thread needs its own profile. A shared --user-data-dir makes Chrome + // fail to open localhost when tests run in parallel. + String profileDir = System.getProperty("java.io.tmpdir") + "admin-ui-chrome-" + + Thread.currentThread().getId(); + options.addArguments("--disable-web-security", "--user-data-dir=" + profileDir, + "--proxy-bypass-list=<-loopback>"); Map prefs = new HashMap(); prefs.put("profile.block_third_party_cookies", Boolean.FALSE); prefs.put("profile.cookie_controls_mode", Integer.valueOf(0)); options.setExperimentalOption("prefs", prefs); } - logger.info("Chrome started with web security disabled. user-data-dir=" + userDataDir.getAbsolutePath()); WebDriver threadDriver = new ChromeDriver(options); driverThread.set(threadDriver); // Set driver for this thread @@ -143,21 +143,34 @@ private void injectAdminToken(WebDriver threadDriver) { if (token == null || token.isEmpty()) { throw new IllegalStateException("Admin token was empty. Check Kernel.properties client id and secret."); } - String apiUrl = BaseTestCaseFunc.ApplnURI.endsWith("/") ? BaseTestCaseFunc.ApplnURI - : BaseTestCaseFunc.ApplnURI + "/"; + String apiUrl = cookieHost(); + boolean https = apiUrl.startsWith("https://"); Map cookie = new HashMap(); cookie.put("name", BaseTestCaseFunc.COOKIENAME); cookie.put("value", token); cookie.put("url", apiUrl); cookie.put("path", "/"); - cookie.put("secure", Boolean.TRUE); - cookie.put("sameSite", "None"); + cookie.put("secure", Boolean.valueOf(https)); + cookie.put("sameSite", https ? "None" : "Lax"); ChromeDriver chromeDriver = (ChromeDriver) threadDriver; chromeDriver.executeCdpCommand("Network.enable", new HashMap()); chromeDriver.executeCdpCommand("Network.setCookie", cookie); logger.info("Stored Authorization cookie for " + apiUrl); } + /** + * The UI calls masterdatabaseurl (local admin-service). A cookie stored on the + * dev2 API host is not sent with those requests. + */ + private String cookieHost() { + String master = ConfigManager.getMasterdatabaseurl(); + String host = (master != null && !master.isEmpty()) ? master : BaseTestCaseFunc.ApplnURI; + if (!host.endsWith("/")) { + host = host + "/"; + } + return host; + } + private boolean isLocalPortal(String portalUrl) { if (portalUrl == null) { return false; diff --git a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseTestCaseFunc.java b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseTestCaseFunc.java index 290f0de0..3099f6ff 100644 --- a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseTestCaseFunc.java +++ b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/BaseTestCaseFunc.java @@ -168,7 +168,7 @@ public static JSONObject getRequestJson(String filepath) { public static void getLeafeZone() { String token = kernelAuthLib.getTokenByRole("globalAdmin"); - String url=ApplnURI + propsKernel.getProperty("leafzonesURL"); + String url = masterDataUrl(propsKernel.getProperty("leafzonesURL")); JSONObject request = new JSONObject(); Response response = RestClient.getRequestWithCookieAndPathParm(url, request, MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON, "Authorization", token); @@ -179,7 +179,7 @@ public static void getLeafeZone() { @SuppressWarnings("unchecked") public static void mapUserToZone(String user, String zone) { String token = kernelAuthLib.getTokenByRole("globalAdmin"); - String url = ApplnURI + propsKernel.getProperty("zoneMappingUrl"); + String url = masterDataUrl(propsKernel.getProperty("zoneMappingUrl")); org.json.simple.JSONObject actualrequest = getRequestJson(zoneMappingRequest); JSONObject request = new JSONObject(); request.put("zoneCode", RestClient.ZONECODE); @@ -195,7 +195,7 @@ public static void mapUserToZone(String user, String zone) { } public static void mapZone(String user) { String token = kernelAuthLib.getTokenByRole("globalAdmin"); - String url = ApplnURI + propsKernel.getProperty("zoneMappingActivateUrl"); + String url = masterDataUrl(propsKernel.getProperty("zoneMappingActivateUrl")); HashMap map = new HashMap(); map.put("isActive","true"); map.put("userId", user); @@ -207,7 +207,7 @@ public static void mapZone(String user) { public static int getHierarchyNumbers() { KernelAuthentication kernelAuthLib = new KernelAuthentication(); String token = kernelAuthLib.getTokenByRole("admin"); - String url = ApplnURI + props.getProperty("locationHierarchyLevels") + getlang(); + String url = masterDataUrl(props.getProperty("locationHierarchyLevels") + getlang()); Response response = RestClient.getRequestWithCookie(url, MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON, "Authorization", token); String responseString = response.asString(); @@ -230,6 +230,20 @@ public static int getHierarchyNumbers() { return maxHierarchyLevel; } + public static String masterDataUrl(String path) { + String base = ConfigManager.getMasterdatabaseurl(); + if (base == null || base.isEmpty()) { + base = ApplnURI; + } + if (base.endsWith("/")) { + base = base.substring(0, base.length() - 1); + } + if (path != null && !path.startsWith("/")) { + path = "/" + path; + } + return base + path; + } + public static String getlang() { String language=ConfigManager.getloginlang(); if(language.equalsIgnoreCase("sin")) { diff --git a/uitest-admin/src/main/resources/config/Kernel.properties b/uitest-admin/src/main/resources/config/Kernel.properties index 70a9271e..3105d4d0 100644 --- a/uitest-admin/src/main/resources/config/Kernel.properties +++ b/uitest-admin/src/main/resources/config/Kernel.properties @@ -67,6 +67,7 @@ servicesNotDeployed= adminPortalPath=https://admin.dev1.mosip.net/ apiEnvUser=api-internal.dev1 apiInternalEndPoint=https://api-internal.dev1.mosip.net +masterdatabaseurl=http://localhost:8086 #----------------------------------Test data----------------------------------------------------------# adminscenariosToExecute= From e8808537bf1186d177bea6f4fda28883bae4a32d Mon Sep 17 00:00:00 2001 From: Jayesh Kharode Date: Fri, 23 Oct 2026 23:13:43 +0530 Subject: [PATCH 6/6] [MOSIP-45706] Wait for slow localhost pages before row actions and save. Local runs were clicking menus, dates, and save before the page finished rendering. Signed-off-by: Jayesh Kharode Co-authored-by: Cursor --- .../testrig/adminui/utility/Commons.java | 277 ++++++++++++++---- 1 file changed, 226 insertions(+), 51 deletions(-) diff --git a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/Commons.java b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/Commons.java index 8e711564..6facc116 100644 --- a/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/Commons.java +++ b/uitest-admin/src/main/java/io/mosip/testrig/adminui/utility/Commons.java @@ -34,7 +34,7 @@ public static String getDateTime() { } private static WebDriverWait getWait(WebDriver driver) { - return new WebDriverWait(driver, Duration.ofSeconds(20)); + return new WebDriverWait(driver, Duration.ofSeconds(40)); } public static void waitForElementClickable(WebDriver driver, By by) { @@ -43,6 +43,18 @@ public static void waitForElementClickable(WebDriver driver, By by) { public static void waitForElementVisible(WebDriver driver, By by) { getWait(driver).until(ExpectedConditions.visibilityOfElementLocated(by)); + if (isLocalRun(driver)) { + pause(2000); + } + } + + private static boolean isLocalRun(WebDriver driver) { + try { + String url = driver.getCurrentUrl(); + return url != null && (url.contains("localhost") || url.contains("127.0.0.1")); + } catch (Exception e) { + return false; + } } public static void waitForPageLoad(WebDriver driver) { @@ -68,6 +80,7 @@ private static String getLocator(By by) { public static void filter(WebDriver driver, By by, String data, String description) throws IOException { AdminExtentReportManager.logStep(description + " | Filter Value → " + data); try { + returnToList(driver); logger.info("Inside Filter " + by + " with data: " + data); Commons.click(driver, By.id("Filter"), "Click Filter"); waitForElementVisible(driver, by); @@ -90,6 +103,7 @@ public static void filter(WebDriver driver, By by, String data) throws IOExcepti public static void filterCenter(WebDriver driver, By by, String data, String description) throws IOException { AdminExtentReportManager.logStep(description + " | Filter Value → " + data); try { + returnToList(driver); Commons.click(driver, By.id("Filter"), "Click Filter"); Commons.dropdowncenter(driver, by, data); Commons.click(driver, By.id("applyTxt"), "Apply Filter"); @@ -100,12 +114,96 @@ public static void filterCenter(WebDriver driver, By by, String data, String des JavascriptExecutor executor = (JavascriptExecutor) driver; executor.executeScript("arguments[0].click();", driver.findElement(by)); } + new WebDriverWait(driver, Duration.ofSeconds(40)) + .until(ExpectedConditions.presenceOfElementLocated(By.id("ellipsis-button0"))); } public static void filterCenter(WebDriver driver, By by, String data) throws IOException { filterCenter(driver, by, data, "Filter Center by value"); } + /** + * Opens the first-row actions menu and leaves it open. The row is often still + * refreshing when the button first appears, and a second click toggles the menu shut. + */ + public static void clickEllipsis(WebDriver driver) throws IOException { + openRowMenu(driver); + } + + public static void openRowAction(WebDriver driver, String actionId) throws IOException { + By action = By.id(actionId); + TimeoutException last = null; + for (int attempt = 0; attempt < 3; attempt++) { + try { + openRowMenu(driver); + new WebDriverWait(driver, Duration.ofSeconds(5)) + .until(ExpectedConditions.elementToBeClickable(action)); + driver.findElement(action).click(); + logger.info("Clicked row action " + actionId); + return; + } catch (TimeoutException e) { + last = e; + logger.info("Row menu closed before " + actionId + ", retry " + (attempt + 1)); + } + } + throw last; + } + + private static void openRowMenu(WebDriver driver) throws IOException { + By ellipsis = By.id("ellipsis-button0"); + By menu = By.cssSelector(".cdk-overlay-container .mat-menu-panel"); + logStep("Clicked on Ellipsis button for the first record", ellipsis); + logger.info("Clicking " + ellipsis); + new WebDriverWait(driver, Duration.ofSeconds(40)).until(d -> ellipsisStable(d, ellipsis)); + WebElement button = driver.findElement(ellipsis); + ((JavascriptExecutor) driver).executeScript( + "arguments[0].scrollIntoView({block:'center', inline:'nearest', behavior:'instant'});", button); + if (isLocalRun(driver)) { + pause(2000); + } + button = driver.findElement(ellipsis); + ((JavascriptExecutor) driver).executeScript("arguments[0].click();", button); + new WebDriverWait(driver, Duration.ofSeconds(8)) + .until(ExpectedConditions.visibilityOfElementLocated(menu)); + } + + private static void returnToList(WebDriver driver) { + By ellipsis = By.id("ellipsis-button0"); + if (!driver.findElements(By.id("applyTxt")).isEmpty()) { + List dialogCancel = driver.findElements(By.id("cancel")); + if (!dialogCancel.isEmpty()) { + ((JavascriptExecutor) driver).executeScript("arguments[0].click();", + dialogCancel.get(dialogCancel.size() - 1)); + pause(500); + } + } + for (int i = 0; i < 4 && driver.findElements(ellipsis).isEmpty(); i++) { + List cancels = driver.findElements(By.id("cancel")); + if (cancels.isEmpty()) { + break; + } + ((JavascriptExecutor) driver).executeScript("arguments[0].click();", cancels.get(0)); + pause(1500); + } + } + + private static void pause(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private static boolean ellipsisStable(WebDriver driver, By ellipsis) { + try { + WebElement button = driver.findElement(ellipsis); + return button.isEnabled(); + } catch (StaleElementReferenceException | NoSuchElementException e) { + return false; + } + } + public static void click(WebDriver driver, By by, String description) throws IOException { logStep(description, by); logger.info("Clicking " + by); @@ -125,22 +223,66 @@ public static void click(WebDriver driver, By by) throws IOException { click(driver, by, "Clicked element"); } + private static void clickSave(WebDriver driver, By save) throws IOException { + waitForElementVisible(driver, save); + WebElement button = driver.findElement(save); + ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView({block:'center'});", button); + pause(2000); + Commons.click(driver, save, "Clicked on Create button to save changes"); + } + + /** + * Types into the field and clicks save only after the full value is present and has stayed there. + */ + private static void enterThenSave(WebDriver driver, By field, String data) throws IOException { + Commons.enter(driver, field, data, "Entered new data: " + data); + new WebDriverWait(driver, Duration.ofSeconds(20)).until(d -> data.equals(readFieldValue(d, field))); + logger.info("Field value entered, waiting 5 seconds before save"); + pause(5000); + if (!data.equals(readFieldValue(driver, field))) { + throw new TimeoutException("Field value was not stable before save: " + field); + } + clickSave(driver, By.id("createButton")); + } + + private static String readFieldValue(WebDriver driver, By field) { + try { + WebElement element = driver.findElement(field); + Object value = ((JavascriptExecutor) driver).executeScript("return arguments[0].value;", element); + if (value == null || value.toString().isEmpty()) { + String attribute = element.getAttribute("value"); + return attribute == null ? "" : attribute; + } + return value.toString(); + } catch (StaleElementReferenceException | NoSuchElementException e) { + return ""; + } + } + public static void enter(WebDriver driver, By by, String value, String description) throws IOException { logStep(description + " | Value → " + value, by); logger.info("Entering " + by + value); - try { - driver.findElement(by).clear(); + if ("fileInput".equals(idOf(by))) { driver.findElement(by).sendKeys(value); + return; + } + try { + waitForElementVisible(driver, by); + WebElement element = driver.findElement(by); + element.clear(); + element.sendKeys(value); + new WebDriverWait(driver, Duration.ofSeconds(15)).until(d -> fieldContains(d, by, value)); } catch (StaleElementReferenceException sere) { Reporter.log("

"); driver.findElement(by).sendKeys(value); } catch (TimeoutException toe) { - Reporter.log("

"); - - driver.findElement(by).sendKeys(value); - logger.info("Element identified by " + by.toString() + " was not clickable after 20 seconds"); + logger.info("Element " + by + " was not ready, waiting and retrying entry"); + pause(2000); + waitForElementVisible(driver, by); + WebElement element = driver.findElement(by); + element.clear(); + element.sendKeys(value); } catch (Exception e) { Reporter.log("

"); @@ -153,6 +295,16 @@ public static void enter(WebDriver driver, By by, String value) throws IOExcepti enter(driver, by, value, "Entered text"); } + private static String idOf(By by) { + String locator = by == null ? "" : by.toString(); + int split = locator.lastIndexOf(": "); + return split < 0 ? locator : locator.substring(split + 2); + } + + private static boolean fieldContains(WebDriver driver, By by, String value) { + return value.equals(readFieldValue(driver, by)); + } + public static void enterSensitive(WebDriver driver, By by, String value, String description) throws IOException { logStep(description + " | Value → ****", by); logger.info("Entering sensitive value into element " + by); @@ -220,18 +372,18 @@ public static void dropdown(WebDriver driver, By by, String value, String descri public static void dropdowncenter(WebDriver driver, By by, String value, String description) throws IOException { logStep(description + " | Select dropdown center using value: " + value, by); logger.info("Selecting DropDown By Value " + by + value); - + waitForElementVisible(driver, by); + WebElement input = driver.findElement(by); + input.click(); + input.clear(); + input.sendKeys(value); + By option = By.xpath("//mat-option[contains(normalize-space(.),'" + value + "')]"); try { - waitForElementClickable(driver, by); - click(driver, by, "Clicked dropdown field to view options"); - waitForElementClickable(driver, By.id(value)); - click(driver, By.id(value), "Selected dropdown value with ID: " + value); - - } catch (Exception e) { - Reporter.log("

"); - JavascriptExecutor executor = (JavascriptExecutor) driver; - executor.executeScript("arguments[0].click();", driver.findElement(by)); + new WebDriverWait(driver, Duration.ofSeconds(20)) + .until(ExpectedConditions.elementToBeClickable(option)); + driver.findElement(option).click(); + } catch (TimeoutException e) { + logger.info("Autocomplete option not shown for " + value + ", continuing with typed value"); } } @@ -261,7 +413,7 @@ public static String getTestData() { public static void deactivate(WebDriver driver, String description) throws IOException { AdminExtentReportManager.logStep(description); - Commons.click(driver, By.id("ellipsis-button0"), "Clicked on Ellipsis button for the first record"); + Commons.clickEllipsis(driver); waitForElementClickable(driver, By.id("Deactivate0")); Commons.click(driver, By.id("Deactivate0"), "Clicked on Deactivate for the first record"); if (isElementDisplayed(By.id("confirmpopup"))) { @@ -277,7 +429,7 @@ public static void deactivate(WebDriver driver) throws IOException { public static void activate(WebDriver driver, String description) throws IOException { AdminExtentReportManager.logStep(description); - Commons.click(driver, By.id("ellipsis-button0"), "Clicked on Ellipsis button for the first record"); + Commons.clickEllipsis(driver); waitForElementClickable(driver, By.id("Activate0")); Commons.click(driver, By.id("Activate0"), "Clicked on Activate for the first record"); if (isElementDisplayed(By.id("confirmpopup"))) { @@ -295,13 +447,12 @@ public static void edit(WebDriver driver, String data, By by, String description logStep(description + " | New Value → " + data, by); try { - Commons.click(driver, By.id("ellipsis-button0"), "Clicked on Ellipsis button for the first record"); - Commons.click(driver, By.id("Edit0"), "Clicked on Edit for the first record"); - Assert.assertNotEquals(data, driver.findElement(by).getText(), + openRowAction(driver, "Edit0"); + waitForElementVisible(driver, by); + Assert.assertNotEquals(data, driver.findElement(by).getAttribute("value"), "Verified existing value is different from new data"); driver.findElement(by).clear(); - Commons.enter(driver, by, data, "Entered new data: " + data); - Commons.click(driver, By.id("createButton"), "Clicked on Create button to save changes"); + enterThenSave(driver, by, data); if (isElementDisplayed(By.id("confirmpopup"))) Commons.click(driver, By.id("confirmpopup"), "Clicked on Confirm popup if displayed"); Commons.click(driver, By.id("confirmmessagepopup"), "Clicked on Confirm Message popup after editing"); @@ -322,14 +473,12 @@ public static void editRes(WebDriver driver, String data, By by, String descript logStep(description + " | New Value → " + data, by); try { - Commons.click(driver, By.id("ellipsis-button0"), "Clicked on Ellipsis button for the first record"); - Commons.click(driver, By.id("Edit0"), "Clicked on Edit for the first record"); + openRowAction(driver, "Edit0"); waitForElementVisible(driver, by); - Assert.assertNotEquals(data, driver.findElement(by).getText(), + Assert.assertNotEquals(data, driver.findElement(by).getAttribute("value"), "Verified existing value is different from new data"); driver.findElement(by).clear(); - Commons.enter(driver, by, data, "Entered new data: " + data); - Commons.click(driver, By.id("createButton"), "Clicked on Create button to save changes"); + enterThenSave(driver, by, data); if (isElementDisplayed(By.id("confirmpopup"))) Commons.click(driver, By.id("confirmpopup"), "Clicked on Confirm popup if displayed"); Commons.click(driver, By.id("confirmmessagepopup"), "Clicked on Confirm Message popup after editing"); @@ -352,8 +501,7 @@ public static void editCenter(WebDriver driver, String data, By by, String descr logStep(description + " | New Value → " + data, by); try { - Commons.click(driver, By.id("ellipsis-button0"), "Clicked on Ellipsis button for the first record"); - Commons.click(driver, By.id("Edit0"), "Clicked on Edit for the first record"); + openRowAction(driver, "Edit0"); waitForPageLoad(driver); Assert.assertNotEquals(data, driver.findElement(by).getText(), "Verified existing value is different from new data"); @@ -385,10 +533,7 @@ public static void editCenter(WebDriver driver, String data, By by) throws IOExc public static void create(WebDriver driver, String description) throws IOException { AdminExtentReportManager.logStep(description); - Commons.click(driver, By.xpath("//button[@id='createButton']"), "Clicked on Create button"); - if (isElementDisplayed(By.id("confirmpopup"))) - Commons.click(driver, By.id("confirmpopup"), "Clicked on Confirm popup"); - Commons.click(driver, By.id("confirmmessagepopup"), "Clicked on Confirm Message popup"); + confirmSubmit(driver); logger.info("Click create"); } @@ -398,20 +543,43 @@ public static void create(WebDriver driver) throws IOException { public static void createRes(WebDriver driver, String description) throws IOException { AdminExtentReportManager.logStep(description); - Commons.click(driver, By.xpath("//button[@id='createButton']"), "Clicked on Create button"); - if (isElementDisplayed(By.id("confirmpopup"))) - Commons.click(driver, By.id("confirmpopup"), "Clicked on Confirm popup if displayed"); - Commons.click(driver, By.id("confirmmessagepopup"), "Clicked on Confirm Message popup"); + confirmSubmit(driver); logger.info("Click and confirm"); } + private static void confirmSubmit(WebDriver driver) throws IOException { + By create = By.xpath("//button[@id='createButton']"); + By confirm = By.id("confirmpopup"); + By message = By.id("confirmmessagepopup"); + Commons.click(driver, create, "Clicked on Create button"); + clickIfPresent(driver, confirm, 8); + if (!clickIfPresent(driver, message, 20)) { + logger.info("Success message not shown, submitting the form again"); + Commons.click(driver, create, "Clicked on Create button again"); + clickIfPresent(driver, confirm, 8); + waitForElementVisible(driver, message); + Commons.click(driver, message, "Clicked on Confirm Message popup"); + } + } + + private static boolean clickIfPresent(WebDriver driver, By by, int seconds) { + try { + new WebDriverWait(driver, Duration.ofSeconds(seconds)).until(ExpectedConditions.elementToBeClickable(by)); + driver.findElement(by).click(); + logger.info("Clicked " + by); + return true; + } catch (TimeoutException e) { + return false; + } + } + public static void createRes(WebDriver driver) throws IOException { createRes(driver, "Create Resource"); } public static void decommission(WebDriver driver, String description) throws IOException { AdminExtentReportManager.logStep(description); - Commons.click(driver, By.id("ellipsis-button0"), "Clicked on Ellipsis button for the first record"); + Commons.clickEllipsis(driver); waitForElementClickable(driver, By.id("Decommission0")); Commons.click(driver, By.id("Decommission0"), "Clicked on Decommission for the first record"); if (isElementDisplayed(By.id("confirmpopup"))) @@ -424,6 +592,20 @@ public static void decommission(WebDriver driver) throws IOException { decommission(driver, "Decommission Record"); } + private static void selectCalendarDay(String dayText) throws IOException { + By dayCell = By.xpath( + "//div[contains(@class,'mat-calendar-body-cell-content') and normalize-space(text())='" + dayText + + "']"); + if (isLocalRun(driver())) { + pause(2000); + } + new WebDriverWait(driver(), Duration.ofSeconds(15)).until(ExpectedConditions.elementToBeClickable(dayCell)); + driver().findElement(dayCell).click(); + new WebDriverWait(driver(), Duration.ofSeconds(10)) + .until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector("mat-calendar"))); + logger.info("Selected day: " + dayText); + } + public static String getPreAppend() { String preappend = null; try { @@ -466,9 +648,7 @@ public static void calendar(String date, String description) throws IOException List cli = driver().findElements(By.xpath("//*[@class='mat-calendar-body-cell-content']")); cli.get(month - 1).click(); - - waitForElementClickable(driver(), By.xpath("//*[text()='" + day + "']")); - Commons.click(driver(), By.xpath("//*[text()='" + day + "']"), "Selected day: " + day); + selectCalendarDay(String.valueOf(day)); } catch (Exception e) { Reporter.log("

"); @@ -501,12 +681,7 @@ public static void calendar(String date, String locale, String description) thro // Select month (index based to avoid translation issues) List months = driver().findElements(By.xpath("//*[@class='mat-calendar-body-cell-content']")); months.get(month - 1).click(); - - // Select day (convert if Arabic locale) - String dayText = convertDigits(String.valueOf(day), locale); - waitForElementClickable(driver(), By.xpath("//*[normalize-space(text())='" + dayText + "']")); - Commons.click(driver(), By.xpath("//*[normalize-space(text())='" + dayText + "']"), - "Selected day: " + dayText); + selectCalendarDay(convertDigits(String.valueOf(day), locale)); } catch (Exception e) { Reporter.log("

");