diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 7c5090750b6..f4c97ebc7a2 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -176,13 +176,13 @@ jobs: ${{ github.workspace }}/HPCC-Platform/.github/workflows/timeoutcmd if-no-files-found: error - - name: Upload UI Test Files + - name: Upload ECL Watch UI Test Files if: ${{ inputs.upload-package == true }} uses: actions/upload-artifact@v3 with: - name: ${{ inputs.asset-name }}-ui_test-files + name: ${{ inputs.asset-name }}-ecl_watch_ui_tests path: | - ${{ github.workspace }}/HPCC-Platform/esp/src/test-ui/**/* + ${{ github.workspace }}/HPCC-Platform/esp/src/test-ui/tests/**/* if-no-files-found: error - name: Upload Error Logs diff --git a/.github/workflows/test-ui-gh_runner.yml b/.github/workflows/test-ui-gh_runner.yml index 902b02f678b..76bd7c2f540 100644 --- a/.github/workflows/test-ui-gh_runner.yml +++ b/.github/workflows/test-ui-gh_runner.yml @@ -33,17 +33,17 @@ jobs: - name: Download UI Test Files uses: actions/download-artifact@v3 with: - name: ${{ inputs.asset-name }}-ui_test-files - path: ${{ inputs.asset-name }}-ui_test-files + name: ${{ inputs.asset-name }}-ecl_watch_ui_tests + path: ${{ inputs.asset-name }}-ecl_watch_ui_tests - name: Check ECLWatch UI Test Directory id: check run: | - if [[ ! -d ${{ inputs.asset-name }}-ui_test-files ]] + if [[ ! -d ${{ inputs.asset-name }}-ecl_watch_ui_tests ]] then - echo "ECLWatch UI ${{ inputs.asset-name }}-ui_test-files directory missing." + echo "ECLWatch UI ${{ inputs.asset-name }}-ecl_watch_ui_tests directory missing." else - javaFilesCount=$(find ${{ inputs.asset-name }}-ui_test-files/ -iname '*.java' -type f -print | wc -l ) + javaFilesCount=$(find ${{ inputs.asset-name }}-ecl_watch_ui_tests/ -iname '*.java' -type f -print | wc -l ) echo "Number of test java files is $javaFilesCount" if [[ ${javaFilesCount} -eq 0 ]] then @@ -95,7 +95,7 @@ jobs: chmod +x ./${{ inputs.asset-name }}-support-files/* sudo cp ./${{ inputs.asset-name }}-support-files/* /opt/HPCCSystems/bin - chmod +x ./${{ inputs.asset-name }}-ui_test-files/* + chmod +x ./${{ inputs.asset-name }}-ecl_watch_ui_tests/* - name: Start HPCC-Platform shell: "bash" @@ -136,34 +136,41 @@ jobs: run: | wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo apt-get install -y ./google-chrome-stable_current_amd64.deb - wget https://chromedriver.storage.googleapis.com/2.41/chromedriver_linux64.zip - unzip chromedriver_linux64.zip - sudo mv chromedriver /usr/bin/chromedriver + wget https://storage.googleapis.com/chrome-for-testing-public/126.0.6478.126/linux64/chromedriver-linux64.zip + unzip chromedriver-linux64.zip -d chromedriver + sudo mv chromedriver/chromedriver-linux64/chromedriver /usr/bin/chromedriver sudo chown root:root /usr/bin/chromedriver sudo chmod +x /usr/bin/chromedriver - wget https://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar - wget http://www.java2s.com/Code/JarDownload/testng/testng-6.8.7.jar.zip - unzip testng-6.8.7.jar.zip - - - name: Run Tests - timeout-minutes: 10 # generous, current runtime is ~1min, this should be increased if new tests are added + wget https://repo1.maven.org/maven2/org/testng/testng/7.7.1/testng-7.7.1.jar + wget https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.17.0/jackson-annotations-2.17.0.jar + wget https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.17.0/jackson-core-2.17.0.jar + wget https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.17.0/jackson-databind-2.17.0.jar + wget https://repo1.maven.org/maven2/com/beust/jcommander/1.82/jcommander-1.82.jar + wget https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.22.0/selenium-java-4.22.0.zip + wget https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar + wget https://repo1.maven.org/maven2/org/slf4j/slf4j-simple/1.7.30/slf4j-simple-1.7.30.jar + unzip selenium-java-4.22.0.zip -d selenium-libs + + - name: Run ECL Watch UI Tests + timeout-minutes: 80 # generous, current runtime is ~38 minutes, this should be increased if new tests are added if: steps.check.outputs.runtests shell: "bash" run: | - export CLASSPATH=".:${{ github.workspace }}/selenium-server-standalone-3.141.59.jar:${{ github.workspace }}/testng-6.8.7.jar" - pushd ${{ inputs.asset-name }}-ui_test-files - ./run.sh tests http://localhost:8010 > eclWatchUiTest.log 2>&1 - retCode=$? - echo "UI test done" - [[ $retCode -ne 0 ]] && exit 1 + export CLASSPATH=".:${{ inputs.asset-name }}-ecl_watch_ui_tests:${{ github.workspace }}/selenium-libs/*:${{ github.workspace }}/testng-7.7.1.jar:${{ github.workspace }}/jackson-annotations-2.17.0.jar:${{ github.workspace }}/jackson-core-2.17.0.jar:${{ github.workspace }}/jackson-databind-2.17.0.jar:${{ github.workspace }}/jcommander-1.82.jar:${{ github.workspace }}/slf4j-api-1.7.30.jar:${{ github.workspace }}/slf4j-simple-1.7.30.jar" + pushd ${{ inputs.asset-name }}-ecl_watch_ui_tests + find . -iname '*.java' -type f -print -exec javac {} \; + java framework.TestRunner detail + echo "ECL Watch UI test done" + lines=$(wc -l < error_ecl_test.log) + [[ $lines -ne 0 ]] && exit 1 popd - - name: eclwatch-ui-test-logs-artifact + - name: Upload ECL Watch UI Test Logs To Artifact if: ${{ failure() || cancelled() }} uses: actions/upload-artifact@v3 with: - name: ${{ inputs.asset-name }}-ui_test-logs + name: ${{ inputs.asset-name }}-ecl_watch_ui_test_logs path: | - ${{ inputs.asset-name }}-ui_test-files/eclWatchUiTest.log + ${{ inputs.asset-name }}-ecl_watch_ui_tests/*.log /home/runner/HPCCSystems-regression/log/*.json if-no-files-found: error diff --git a/esp/src/test-ui/tests/Activities.java b/esp/src/test-ui/tests/Activities.java index d160ae8f60c..35686098d3e 100644 --- a/esp/src/test-ui/tests/Activities.java +++ b/esp/src/test-ui/tests/Activities.java @@ -24,7 +24,7 @@ public static void main(String[] args) throws IOException, InterruptedException Capabilities caps = ((RemoteWebDriver) driver).getCapabilities(); String browserName = caps.getBrowserName(); - String browserVersion = caps.getVersion(); + //String browserVersion = caps.getVersion(); // System.out.println(browserName+" "+browserVersion); driver.get(args[0]); diff --git a/esp/src/test-ui/tests/framework/README.md b/esp/src/test-ui/tests/framework/README.md index 0f56ebe39b8..78ab59a1b9b 100644 --- a/esp/src/test-ui/tests/framework/README.md +++ b/esp/src/test-ui/tests/framework/README.md @@ -1,4 +1,4 @@ -Project: An Automated ECL Watch Test Suite +### Project: An Automated ECL Watch Test Suite This project's code begins with the TestRunner.java file. The main method in this class loads all the Java classes created for writing test cases for specific web pages of the ECL Watch UI and then runs the tests in those classes @@ -8,4 +8,42 @@ The names of the Java classes that the TestRunner class needs to load should be file. Each Java class created to write tests for specific web pages should have at least one method annotated with @Test. The -code for each class starts to run from this method. \ No newline at end of file +code for each class starts to run from this method. + +#### Important Note: ChromeDriver Version Compatibility + +If the Chrome browser version updates in the future, it's crucial to ensure that the corresponding ChromeDriver version is also updated. Failure to do so may cause tests to fail due to compatibility issues between the browser and driver. Always verify and update ChromeDriver to the latest version whenever running tests to maintain compatibility and ensure smooth test execution. + +#### CLI Arguments for TestRunner.java + +While running the test suite, you can pass arguments in this way -> "-l log_level -p path". +- "log_level" is of two types "debug" and "detail" +- "debug" means generate error log file with a debug log file. +- "detail" means generate error log file with a detailed debug file. +- If no -l and log_level is passed in the argument, only error log will be generated +- "path" is the path of the folder where the json files are +- The code will log an error if the '-p' and 'path' arguments are not provided, as the JSON folder path is required for the test suite. + +path could be something like: + +for GitHub Actions -> /home/runner/HPCCSystems-regression/log/ + +for local machine -> C:/Users/nisha/Documents/Internship/Work/files/ + +So an example of complete CLI arguments would look like this: + +-l debug -p C:/Users/nisha/Documents/Internship/Work/files/ + +Below are the dependencies used in the project: + +- https://repo1.maven.org/maven2/org/testng/testng/7.7.1/testng-7.7.1.jar +- https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.17.0/jackson-annotations-2.17.0.jar +- https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.17.0/jackson-core-2.17.0.jar +- https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.17.0/jackson-databind-2.17.0.jar +- https://repo1.maven.org/maven2/com/beust/jcommander/1.82/jcommander-1.82.jar +- https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.17.0/selenium-server-4.17.0.jar + +Notes: +1. Users need to run these tests with regression test suite only. +2. Code should be updated accordingly if selenium server jar updates. +3. For future testing developers, custom class names or attributes defined by UI developers can change frequently during updates or redesigns. However, standard attributes that are part of the HTML specifications (such as id, type, value, href, aria-sort, aria-disabled, etc.) are much more stable. Therefore, it is advisable to use only standard HTML attributes to access web elements. This approach ensures that test cases remain consistent and are less likely to break due to UI changes. \ No newline at end of file diff --git a/esp/src/test-ui/tests/framework/TestRunner.java b/esp/src/test-ui/tests/framework/TestRunner.java index 3be28b05a87..1b8c3399593 100644 --- a/esp/src/test-ui/tests/framework/TestRunner.java +++ b/esp/src/test-ui/tests/framework/TestRunner.java @@ -3,66 +3,63 @@ import framework.config.Config; import framework.config.TestClasses; import framework.model.TestClass; -import framework.setup.TestInjector; import framework.utility.Common; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.chrome.ChromeDriver; -import org.openqa.selenium.chrome.ChromeOptions; -import org.openqa.selenium.remote.RemoteWebDriver; import org.testng.TestNG; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URI; import java.util.ArrayList; import java.util.List; -import java.util.logging.FileHandler; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.logging.SimpleFormatter; + public class TestRunner { public static void main(String[] args) { - Logger logger = setupLogger(); - WebDriver driver = setupWebDriver(); + try { + getLogLevelAndJSONFolderPath(args); + Common.initializeLoggerAndDriver(); + + if (Common.driver != null) { + TestNG testng = new TestNG(); + testng.setTestClasses(loadClasses()); + testng.run(); + Common.driver.quit(); + } - TestNG testng = new TestNG(); - testng.setTestClasses(loadClasses()); - testng.addListener(new TestInjector(logger, driver)); - testng.run(); - driver.quit(); + } catch (Exception e) { + Common.logError("Exception occurred in TestRunner.java: " + e.getMessage()); + } } - private static WebDriver setupWebDriver() { - - ChromeOptions chromeOptions = new ChromeOptions(); - chromeOptions.addArguments("--headless"); // sets the ChromeDriver to run in headless mode, meaning it runs without opening a visible browser window. - chromeOptions.addArguments("--no-sandbox"); // disables the sandbox security feature in Chrome. - chromeOptions.addArguments("--log-level=3"); // sets the log level for the ChromeDriver. Level 3 corresponds to errors only. - - System.setProperty("webdriver.chrome.silentOutput", "true"); // suppresses the logs generated by the ChromeDriver - - WebDriver driver; - - if (Common.isRunningOnLocal()) { - System.setProperty("webdriver.chrome.driver", Config.PATH_LOCAL_CHROME_DRIVER); // sets the system property to the path of the ChromeDriver executable. - try { - driver = new RemoteWebDriver(URI.create(Config.LOCAL_SELENIUM_SERVER).toURL(), chromeOptions); - } catch (MalformedURLException e) { - throw new RuntimeException(e); + // Parses the command-line arguments to set the log level and JSON folder path. + // The method checks for the presence of '-l' (log_level) and '-p' (path) arguments. + // Logs an error if the '-p' argument is not provided, as the JSON path is required. + // path is the path of the folder where the json files are + // log level is of two types "debug" and "detail" + // "debug" means generate error log file with a debug log file. + // "detail" means generate error log file with a detailed debug file. + // if no -l and log level is passed in the argument, only error log will be generated + + public static void getLogLevelAndJSONFolderPath(String[] args) { // -l -p + + String log_level = null; + String path = null; + + for (int i = 0; i < args.length; i++) { + if ("-l".equals(args[i]) && i + 1 < args.length) { + log_level = args[++i]; + } else if ("-p".equals(args[i]) && i + 1 < args.length) { + path = args[++i]; } - } else { - System.setProperty("webdriver.chrome.driver", Config.PATH_GH_ACTION_CHROME_DRIVER); - driver = new ChromeDriver(chromeOptions); } - //Capabilities caps = ((RemoteWebDriver) driver).getCapabilities(); - //String browserName = caps.getBrowserName(); - //String browserVersion = caps.getBrowserVersion(); - //System.out.println(browserName+" "+browserVersion); + if (log_level != null) { + Config.LOG_LEVEL = log_level; + } - return driver; + if (path != null) { + Config.PATH_FOLDER_JSON = path; + } else { + Common.logError("Error: JSON folder path is required. Use -p to specify the path."); + } } private static Class[] loadClasses() { @@ -71,28 +68,11 @@ private static Class[] loadClasses() { for (TestClass testClass : TestClasses.testClassesList) { try { classes.add(Class.forName(testClass.getPath())); - } catch (ClassNotFoundException e) { - System.err.println(e.getMessage()); + } catch (Exception e) { + Common.logError("Failure: Error in loading classes: " + e.getMessage()); } } return classes.toArray(new Class[0]); } - - private static Logger setupLogger() { - Logger logger = Logger.getLogger(TestRunner.class.getName()); - try { - FileHandler fileHandler = new FileHandler(Config.LOG_FILE); - SimpleFormatter formatter = new SimpleFormatter(); - fileHandler.setFormatter(formatter); - logger.addHandler(fileHandler); - logger.setUseParentHandlers(false); - logger.setLevel(Level.ALL); - } catch (IOException e) { - System.err.println("Failed to setup logger: " + e.getMessage()); - } - - Logger.getLogger("org.openqa.selenium").setLevel(Level.OFF); // turn off all logging from the Selenium WebDriver. - return logger; - } } diff --git a/esp/src/test-ui/tests/framework/config/Config.java b/esp/src/test-ui/tests/framework/config/Config.java index 98b8311ac28..90ede35a86e 100644 --- a/esp/src/test-ui/tests/framework/config/Config.java +++ b/esp/src/test-ui/tests/framework/config/Config.java @@ -2,19 +2,28 @@ public class Config { - public static final String LOG_FILE = "errorLog.log"; + public static final String LOG_FILE_ERROR = "error_ecl_test.log"; + public static final String LOG_FILE_DEBUG = "debug_ecl_test.log"; + public static final String LOG_FILE_DETAIL = "detail_ecl_test.log"; public static final String LOCAL_OS = "Windows"; public static final String LOCAL_USER_PROFILE = "C:\\Users\\nisha"; - public static final String PATH_LOCAL_CHROME_DRIVER = "C:/Users/nisha/Documents/Internship/Work/jars/chromeDriver"; + public static final String PATH_LOCAL_CHROME_DRIVER = "C:/Users/nisha/Documents/Internship/Work/jars/chromeDriver/chromedriver.exe"; public static final String PATH_GH_ACTION_CHROME_DRIVER = "/usr/bin/chromedriver"; - public static final String PATH_LOCAL_WORKUNITS_JSON = "C:/Users/nisha/Documents/Internship/Work/files/workunits.json"; - public static final String PATH_GH_ACTION_WORKUNITS_JSON = ""; - public static final String LOCAL_SELENIUM_SERVER = "http://localhost:4444/wd/hub"; - public static final String LOCAL_IP = "http://192.168.0.221:8010/"; - public static final String GITHUB_ACTION_IP = "http://127.0.0.1:8010/"; - public static final String ACTIVITIES_URL = "esp/files/index.html#/activities"; - public static final String ECL_WORK_UNITS_URL = "esp/files/index.html#/workunits"; public static final int[] dropdownValues = {10, 25, 50, 100, 250, 500, 1000}; public static final int MALFORMED_TIME_STRING = -1; - public static final int WAIT_TIME_IN_SECONDS = 4; + public static final int WAIT_TIME_IN_SECONDS = 1; + public static final int WAIT_TIME_THRESHOLD_IN_SECONDS = 20; + public static final String TEST_DESCRIPTION_TEXT = "Testing Description"; + public static final boolean TEST_DETAIL_PAGE_FIELD_NAMES_ALL = true; + public static final boolean TEST_WU_DETAIL_PAGE_DESCRIPTION_ALL = true; + public static final boolean TEST_WU_DETAIL_PAGE_PROTECTED_ALL = true; + public static final boolean TEST_DETAIL_PAGE_TAB_CLICK_ALL = true; + + // these values are set in the beginning in the TestRunner.java file + public static String PATH_FOLDER_JSON = ""; + public static String LOG_LEVEL = ""; + + public static final String WORKUNITS_JSON_FILE_NAME = "workunits.json"; + public static final String DFU_WORKUNITS_JSON_FILE_NAME = "dfu-workunits.json"; + public static final String FILES_JSON_FILE_NAME = "files.json"; } diff --git a/esp/src/test-ui/tests/framework/config/TestClasses.java b/esp/src/test-ui/tests/framework/config/TestClasses.java index 2a1a3fb1faf..08d504b5cf2 100644 --- a/esp/src/test-ui/tests/framework/config/TestClasses.java +++ b/esp/src/test-ui/tests/framework/config/TestClasses.java @@ -6,8 +6,10 @@ public class TestClasses { + // ActivitiesTest class should always be the first class to load, as it gets URLs for all other pages. + public static final List testClassesList = List.of( - //new TestClass("ActivitiesTest", "framework.pages.ActivitiesTest"), + new TestClass("ActivitiesTest", "framework.pages.ActivitiesTest"), new TestClass("ECLWorkUnitsTest", "framework.pages.ECLWorkUnitsTest") ); } diff --git a/esp/src/test-ui/tests/framework/config/URLConfig.java b/esp/src/test-ui/tests/framework/config/URLConfig.java new file mode 100644 index 00000000000..5b8bdea4ca5 --- /dev/null +++ b/esp/src/test-ui/tests/framework/config/URLConfig.java @@ -0,0 +1,54 @@ +package framework.config; + +import framework.model.URLMapping; +import framework.utility.Common; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class URLConfig { + + public static final String LOCAL_SELENIUM_SERVER = "http://localhost:4444/wd/hub"; + public static final String LOCAL_IP = "http://192.168.0.221:8010/"; + public static final String GITHUB_ACTION_IP = "http://127.0.0.1:8010/"; + + public static final String NAV_ACTIVITIES = "Activities"; + public static final String NAV_ECL = "ECL"; + public static final String NAV_FILES = "Files"; + public static final String NAV_PUBLISHED_QUERIES = "Published Queries"; + public static final String NAV_OPERATIONS = "Operations"; + public static final String TAB_ACTIVITIES_ACTIVITIES = "Activities"; + public static final String TAB_ACTIVITIES_EVENT_SCHEDULER = "Event Scheduler"; + public static final String TAB_ECL_WORKUNITS = "Workunits"; + public static final String TAB_ECL_PLAYGROUND = "Playground"; + public static final String TAB_FILES_LOGICAL_FILES = "Logical Files"; + public static final String TAB_FILES_LANDING_ZONES = "Landing Zones"; + public static final String TAB_FILES_WORKUNITS = "Workunits"; + public static final String TAB_FILES_XREF = "XRef (L)"; + public static final String TAB_PUBLISHED_QUERIES_QUERIES = "Queries"; + public static final String TAB_PUBLISHED_QUERIES_PACKAGE_MAPS = "Package Maps"; + public static final String TAB_OPERATIONS_TOPOLOGY = "Topology (L)"; + public static final String TAB_OPERATIONS_DISK_USAGE = "Disk Usage (L)"; + public static final String TAB_OPERATIONS_TARGET_CLUSTERS = "Target Clusters (L)"; + public static final String TAB_OPERATIONS_CLUSTER_PROCESSES = "Cluster Processes (L)"; + public static final String TAB_OPERATIONS_SYSTEM_SERVERS = "System Servers (L)"; + public static final String TAB_OPERATIONS_SECURITY = "Security (L)"; + public static final String TAB_OPERATIONS_DYNAMIC_ESDL = "Dynamic ESDL (L)"; + + public static final String[] navNamesArray = {NAV_ACTIVITIES, NAV_ECL, NAV_FILES, NAV_PUBLISHED_QUERIES, NAV_OPERATIONS}; + public static final Map> tabsListMap = Map.of( + NAV_ACTIVITIES, List.of(TAB_ACTIVITIES_ACTIVITIES, TAB_ACTIVITIES_EVENT_SCHEDULER), + NAV_ECL, List.of(TAB_ECL_WORKUNITS, TAB_ECL_PLAYGROUND), + NAV_FILES, List.of(TAB_FILES_LOGICAL_FILES, TAB_FILES_LANDING_ZONES, TAB_FILES_WORKUNITS, TAB_FILES_XREF), + NAV_PUBLISHED_QUERIES, List.of(TAB_PUBLISHED_QUERIES_QUERIES, TAB_PUBLISHED_QUERIES_PACKAGE_MAPS), + NAV_OPERATIONS, List.of(TAB_OPERATIONS_TOPOLOGY, TAB_OPERATIONS_DISK_USAGE, TAB_OPERATIONS_TARGET_CLUSTERS, + TAB_OPERATIONS_CLUSTER_PROCESSES, TAB_OPERATIONS_SYSTEM_SERVERS, TAB_OPERATIONS_SECURITY, TAB_OPERATIONS_DYNAMIC_ESDL) + ); + + public static final HashMap urlMap = new HashMap<>(); + + static { + urlMap.put(NAV_ACTIVITIES, new URLMapping(NAV_ACTIVITIES, Common.getIP())); + } +} diff --git a/esp/src/test-ui/tests/framework/documentation/ActivitiesTest.png b/esp/src/test-ui/tests/framework/documentation/ActivitiesTest.png index c3b17514907..21131f104bc 100644 Binary files a/esp/src/test-ui/tests/framework/documentation/ActivitiesTest.png and b/esp/src/test-ui/tests/framework/documentation/ActivitiesTest.png differ diff --git a/esp/src/test-ui/tests/framework/documentation/BaseTableTest.png b/esp/src/test-ui/tests/framework/documentation/BaseTableTest.png deleted file mode 100644 index 289db16ed45..00000000000 Binary files a/esp/src/test-ui/tests/framework/documentation/BaseTableTest.png and /dev/null differ diff --git a/esp/src/test-ui/tests/framework/documentation/BaseTableTest1.png b/esp/src/test-ui/tests/framework/documentation/BaseTableTest1.png new file mode 100644 index 00000000000..e2d9d1f6111 Binary files /dev/null and b/esp/src/test-ui/tests/framework/documentation/BaseTableTest1.png differ diff --git a/esp/src/test-ui/tests/framework/documentation/BaseTableTest2.png b/esp/src/test-ui/tests/framework/documentation/BaseTableTest2.png new file mode 100644 index 00000000000..fb247a55573 Binary files /dev/null and b/esp/src/test-ui/tests/framework/documentation/BaseTableTest2.png differ diff --git a/esp/src/test-ui/tests/framework/documentation/Common.png b/esp/src/test-ui/tests/framework/documentation/Common.png index 0d2b76cc7bb..0ed9dbda328 100644 Binary files a/esp/src/test-ui/tests/framework/documentation/Common.png and b/esp/src/test-ui/tests/framework/documentation/Common.png differ diff --git a/esp/src/test-ui/tests/framework/documentation/Config.png b/esp/src/test-ui/tests/framework/documentation/Config.png index e33466b0fb8..883eecb0333 100644 Binary files a/esp/src/test-ui/tests/framework/documentation/Config.png and b/esp/src/test-ui/tests/framework/documentation/Config.png differ diff --git a/esp/src/test-ui/tests/framework/documentation/DesignDocument.md b/esp/src/test-ui/tests/framework/documentation/DesignDocument.md index 268d34458db..30763cceeea 100644 --- a/esp/src/test-ui/tests/framework/documentation/DesignDocument.md +++ b/esp/src/test-ui/tests/framework/documentation/DesignDocument.md @@ -1,12 +1,31 @@ -**The Class Design and Code Flow for ECL Watch Test Suite** +## The Class Design and Code Flow for ECL Watch Test Suite -**1. TestRunner.java** +### TestRunner.java -![img_2.png](TestRunner.png) +![img.png](TestRunner.png) The TestRunner class is responsible for setting up and executing automated tests using the TestNG framework in a -Selenium-based testing environment. It initializes the WebDriver, loads test classes, configures logging, and runs the -tests. +Selenium-based testing environment. It handles the initialization of logging and WebDriver, dynamically loads the +test classes to be executed, and runs the tests. + +Variables: +args: Command line arguments passed to the main method. While running the test suite, you can pass arguments in this way -> "-l log_level -p path". +- "log_level" is of two types "debug" and "detail" +- "debug" means generate error log file with a debug log file. +- "detail" means generate error log file with a detailed debug file. +- If no -l and log_level is passed in the argument, only error log will be generated +- "path" is the path of the folder where the json files are +- The code will log an error if the '-p' and 'path' arguments are not provided, as the JSON folder path is required for the test suite. + +path could be something like: + +for GitHub Actions -> /home/runner/HPCCSystems-regression/log/ + +for local machine -> C:/Users/nisha/Documents/Internship/Work/files/ + +So an example of complete CLI arguments would look like this: + +-l debug -p C:/Users/nisha/Documents/Internship/Work/files/ **Methods:** @@ -14,26 +33,14 @@ tests. The entry point of the application. It performs the following steps: -- Calls setupLogger to configure the logging system. -- Logs test execution details to a specified file. -- Calls setupWebDriver to initialize the WebDriver based on the environment (local or remote). -- Creates an instance of TestNG. +- Calls getLogLevelAndJSONFolderPath to get the log level and the path of JSON files from CLI arguments and configure them in the Config file. +- Calls Common.initializeLoggerAndDriver to configure the logging system and web driver. +- If the drives sets up properly and is not null, it creates an instance of TestNG. - Sets the test classes to be run by calling loadClasses. -- Adds a TestInjector listener to inject dependencies (logger and driver) into the tests. - Runs the tests using TestNG. - Quits the WebDriver session after the tests have completed. -2. setupWebDriver Method - -Initializes and returns a WebDriver instance configured for headless Chrome operation: - -- Configures ChromeDriver to run in headless mode, disables sandbox security, and sets log level to errors only. -- Suppresses logs generated by ChromeDriver. -- Checks if the tests are running locally using Common.isRunningOnLocal. -- If local, sets up the driver to connect to a local Selenium server. -- If not local, initializes a standard ChromeDriver. - -3. loadClasses Method +2. loadClasses Method Loads the test classes specified in TestClasses.testClassesList: @@ -41,18 +48,23 @@ Loads the test classes specified in TestClasses.testClassesList: - Adds the loaded class to the list classes. - Catches and prints any ClassNotFoundException. -4. setupLogger Method +3. getLogLevelAndJSONFolderPath Method -Configures the logger for logging test execution details: +This method is designed to parse command-line arguments to set the log level and the path to a folder containing JSON files. It validates the presence of these arguments and sets configuration values accordingly. -- Creates a logger for TestRunner. -- Sets up a FileHandler to log messages to a file specified by Config.LOG_FILE. -- Uses a SimpleFormatter to format the log messages. -- Adds the handler to the logger and sets the log level to ALL. -- Catches and prints any IOException. -- Turns off all logging from the Selenium WebDriver to reduce noise in the logs. +- It takes a single parameter, args, which is an array of strings representing the command-line arguments passed to the program. +- Two local variables, log_level and path, are initialized to null. These will hold the values extracted from the command-line arguments. +- A for loop iterates through the args array to examine each argument. +- The loop uses i as the index variable to traverse the array. +- Inside the loop, check if the current argument equals "-l". +- If "-l" is found and there is another argument following it (i + 1 < args.length), assign the value of the next argument to log_level and increment the index i to skip the processed argument. +- Similarly, check if the current argument equals "-p". +- If "-p" is found and there is another argument following it (i + 1 < args.length), assign the value of the next argument to path and increment the index i to skip the processed argument. +- After the loop, check if log_level is not null. If it is not null, assign its value to Config.LOG_LEVEL. +- Check if path is not null. If it is not null, assign its value to Config.PATH_FOLDER_JSON. +- If path is still null after the loop, log an error message indicating that the JSON folder path is required. Use Common.logError to log this error. -**2. TestClasses.java** +### TestClasses.java ![img_4.png](TestClasses.png) @@ -64,7 +76,7 @@ the test classes easily throughout the testing framework. By using the TestClass class, the framework can dynamically load and execute tests, enhancing modularity and maintainability. -**3. TestClass.java** +### TestClass.java ![img_3.png](TestClass.png) @@ -75,74 +87,74 @@ provides a structured way to store and retrieve information about each test clas which can be utilized by other components in the framework for dynamically loading and executing tests. -**4. TestInjector.java** - -![img.png](TestInjector.png) - -The TestInjector class in the framework.setup package implements the -IInvokedMethodListener interface from TestNG. This class is designed to inject -dependencies (such as a logger and a WebDriver instance) into test methods before -they are invoked. It ensures that these dependencies are available to the tests when -they are executed. The TestInjector class provides a way to set up the necessary -environment for test execution by using holders to manage the logger and WebDriver -instances. - -**Methods:** - -1. constructor - -An instance of TestInjector is created with a Logger and a WebDriver. +### URLMapping.java -2. Before Invocation +![img.png](URLMapping.png) -- Before a test method is invoked, the beforeInvocation method is called. -- It checks if the method is a test method. -- If it is a test method, it sets the logger and driver instances in their respective holders. +The URLMapping class in the framework.model package is a model that represents a URL +and its associated metadata. It provides a structured way to store URLs and their hierarchical +relationships, facilitating easy navigation and retrieval of URLs in a web application. -3. After Invocation +#### Variables: +- name: A String representing the name of the navigation element or tab. +- url: A String representing the URL associated with the navigation element or tab. +- urlMappings: A HashMap that maps the names of nested navigation elements or tabs to their corresponding URLMapping objects. -- After a test method is invoked, the afterInvocation method is called. -- Currently, this method does not perform any actions but can be extended for cleanup operations. +### Config.java -**5. WebDriverHolder.java** - -![img.png](WebDriverHolder.png) +![img.png](Config.png) -The WebDriverHolder class in the framework.setup package is a utility class designed to -manage a singleton instance of WebDriver. This class allows the framework to store and -access the WebDriver instance globally, ensuring that all test methods can utilize the -same driver instance. This class is used in the TestInjector class in the beforeInvocation() method. +The Config class in the framework.config package serves as a centralized configuration +repository for the application. It contains various constants that are used throughout +the framework, providing a single point of reference for configuration settings such as +file paths, file names, flags, and other constants. This approach enhances the maintainability and +readability of the code by avoiding hard-coded values scattered across different classes. -**6. LoggerHolder.java** +### URLConfig.java -![img.png](LoggerHolder.png) +![img.png](URLConfig1.png) +![img.png](URLConfig2.png) -The LoggerHolder class in the framework.setup package is a utility class designed to manage a -singleton instance of Logger. This class allows the framework to store and access the -Logger instance globally, ensuring that all test methods and other components can utilize -the same logger instance for consistent logging throughout the application. This class is -used in the TestInjector class in the beforeInvocation() method. +The URLConfig class is designed to manage the URL configurations for different navigation +and tab pages within the test cases. It provides a structured way to store and retrieve +URLs associated with various sections of the ECL Watch UI. This class uses constants to define +URLs and page names, and it leverages a HashMap to store URL mappings, facilitating easy access +to the URLs of different pages and tabs. -**7. Config.java** +- NAV_ACTIVITIES, NAV_ECL, NAV_FILES, NAV_PUBLISHED_QUERIES, NAV_OPERATIONS: Constants representing main navigation sections. +- TAB_ACTIVITIES_ACTIVITIES, TAB_ACTIVITIES_EVENT_SCHEDULER: Tab names under the Activities navigation. +- TAB_ECL_WORKUNITS, TAB_ECL_PLAYGROUND: Tab names under the ECL navigation. +- TAB_FILES_LOGICAL_FILES, TAB_FILES_LANDING_ZONES, TAB_FILES_WORKUNITS, TAB_FILES_XREF: Tab names under the Files navigation. +- TAB_PUBLISHED_QUERIES_QUERIES, TAB_PUBLISHED_QUERIES_PACKAGE_MAPS: Tab names under the Published Queries navigation. +- TAB_OPERATIONS_TOPOLOGY, TAB_OPERATIONS_DISK_USAGE, TAB_OPERATIONS_TARGET_CLUSTERS, TAB_OPERATIONS_CLUSTER_PROCESSES, TAB_OPERATIONS_SYSTEM_SERVERS, TAB_OPERATIONS_SECURITY, TAB_OPERATIONS_DYNAMIC_ESDL: Tab names under the Operations navigation. +- navNamesArray: Array containing all the main navigation names. +- tabsListMap: Map containing lists of tab names associated with each main navigation section. +- urlMap: A HashMap (urlMap) is created to store URL mappings. This map will use the page name as the key and a URLMapping object as the value. The URLMapping object contains the page name, its URL, and another HashMap for nested pages and tabs. +- A static block is used to initialize the urlMap with the initial URL mapping for the Activities navigation. The URL is retrieved using a method from the Common utility class, which handles the dynamic retrieval of the IP address based on the environment whether it is local or GitHub Actions. -![img.png](Config.png) +#### Implementation Steps for URL Management -The Config class in the framework.config package serves as a centralized configuration -repository for the application. It contains various constants that are used throughout -the framework, providing a single point of reference for configuration settings such as -file paths, IPs, and other constants. This approach enhances the maintainability and -readability of the code by avoiding hard-coded values scattered across different classes. +- For each main navigation section, a URLMapping object is created. This object includes the page name and its corresponding URL. Additionally, it contains another HashMap to store URLs for nested tabs and pages. +- Each URLMapping object is stored in the urlMap with the main navigation name as the key. This initial setup ensures that each navigation section has its base URL stored and accessible. +- For instance, for any navigation page, each page has multiple tabs, and within those tabs, there are multiple pages and tabs. This structure facilitates easy access to the URL of a particular page. +- Starting from the Activities page, for each main navigation section, the code iterates over its associated tabs (as defined in tabsListMap). For each tab, a new URLMapping object is created and added to the HashMap within the corresponding URLMapping object of the main navigation section. This creates a tree-like structure, allowing easy access to URLs for both navigation sections and their nested tabs. +- By following these implementation steps, the URLConfig class ensures that all URLs within the application are well-organized and easily accessible through a hierarchical structure. This setup simplifies navigation and URL management within the application, making it easier to handle complex page structures and dynamic URL retrievals. -**8. Common.java** +### Common.java ![img.png](Common.png) The Common class in the framework.utility package provides a set of utility methods that are frequently used throughout the testing framework. These -methods handle common tasks such as checking for text presence on a webpage, -opening a webpage, and determining the environment in which the code is -running (local or GitHub Actions). The class leverages constants from the -Config class to maintain consistency and facilitate configuration management. +methods handle common tasks and leverages constants from the Config class to maintain consistency +and facilitate configuration management. + +Variables: + +- driver: This public static variable stores the WebDriver instance used for interacting with the web browser. +- errorLogger: This public static variable stores a logger instance used for logging error messages. +- specificLogger: This public static variable stores a logger instance used for logging specific messages +based on the provided level (debug or detail). **Methods:** @@ -177,15 +189,93 @@ Determines if the code is running on a local environment. - Checks if the user profile path starts with the value of Config.LOCAL_USER_PROFILE. - Returns true if both conditions are met, indicating a local environment; otherwise, returns false. -5. getUrl Method +5. getIP Method -Constructs the full URL based on the environment (local or GitHub Actions). +The getIP method determines the appropriate IP address to use based on whether the application +is running in a local environment or in a GitHub Actions environment. +This helps in dynamically adjusting the base URL of the application depending on the +execution context. - Calls isRunningOnLocal to check the environment. -- If running locally, returns the URL prefixed with Config.LOCAL_IP. -- If running in GitHub Actions, returns the URL prefixed with Config.GITHUB_ACTION_IP. +- If running locally, returns the URLConfig.LOCAL_IP. +- If running in GitHub Actions, returns the URLConfig.GITHUB_ACTION_IP. + +6. waitForElement Method + +- This method waits for a web element to be present in the DOM. +- Uses WebDriverWait to wait up to 10 seconds for the presence of the specified web element. + +7. logError Method + +- This method logs error messages. +- Prints the error message to the standard error stream. +- Logs the message using errorLogger. + +8. logDebug Method + +- This method logs debug or detailed messages. +- Prints the message to the standard output stream. +- Logs the message using specificLogger if the logging level is INFO or FINE. + +9. logDetail method + +- This method logs detailed messages. +- Prints the message to the standard output stream. +- Logs the message using specificLogger if the logging level is FINE. + +10. initializeLoggerAndDriver Method + +- This method initializes the logger and WebDriver instances. +- Sets up the specificLogger based on the provided argument. +- Sets up the WebDriver instance using setupWebDriver() method. + +11. setupWebDriver Method + +- This method sets up the WebDriver instance based on the environment. +- Configures ChromeOptions for headless mode, no sandbox, and suppressed log output. +- Sets up the WebDriver based on the environment (local or GitHub Actions). +- Logs an error message if an exception occurs during setup. + +12. setupLogger Method + +- This method sets up a logger instance based on the provided log level. +- Configures the logger to disable console logging and set up file handlers for different log levels (error, debug, detail). +- Turns off all logging from Selenium WebDriver. +- Logs an error message if an exception occurs during logger setup. + +13. getAttributeList Method + +- This method retrieves all attributes of a web element. +- Uses JavaScriptExecutor to extract all attributes of the web element. +- Logs an error message if an exception occurs during attribute extraction. + +14. sleepWithTime Method + +- The sleepWithTime method pauses the execution of the current thread for a specified number of seconds. +- It is a simple utility method used to introduce a delay in the execution flow. +- Used to wait for a certain condition or state before proceeding with further execution. + +15. waitForElementToBeClickable Method + +- This method waits until the specified web element is clickable. +- A WebDriverWait instance is created with a timeout duration specified by Config.WAIT_TIME_THRESHOLD_IN_SECONDS. +- The method waits until the element is clickable, using Selenium's ExpectedConditions.elementToBeClickable condition. +- It is useful in scenarios where an element needs to be interacted with, but it might not be immediately clickable due to loading times or other conditions. -**9. TimeUtils.java** +16. waitForElementToBeDisabled Method + +The waitForElementToBeDisabled method waits until the specified web element is disabled by checking its aria-disabled attribute. +The attribute "aria-disabled" is part of the Accessible Rich Internet Applications (ARIA) specification, which is used to improve the accessibility of web pages. +aria-disabled is a standard attribute and is widely used in HTML to indicate whether an element is disabled. It is system-defined, meaning it is part of the standard +HTML specifications and not a custom class or attribute that might change frequently. By using aria-disabled, you ensure that the check for the disabled state is +consistent and less likely to break due to UI changes. Custom class names or attributes defined by developers can change frequently during updates or redesigns, +but standard attributes like aria-disabled are much more stable. + +- A WebDriverWait instance is created with a timeout duration specified by Config.WAIT_TIME_THRESHOLD_IN_SECONDS. +- The method waits until the aria-disabled attribute of the element contains the value "true", indicating that the element is disabled. +- Ensures that an element has transitioned to a disabled state before proceeding with further actions. + +### TimeUtils.java ![img.png](TimeUtils.png) @@ -216,7 +306,7 @@ malformed time strings. returns Config.MALFORMED_TIME_STRING. - If the parsing is successful, the method calculates the total duration in milliseconds -**10. NavigationWebElement** +### NavigationWebElement ![img.png](NavigationWebElement.png) @@ -238,7 +328,7 @@ elements. - The NavigationWebElement class offers a structured way to manage data related to navigation elements in a web application framework. -**11. Java Classes for Representing Workunit JSON Data** +### Java Classes for Representing Workunit JSON Data This section details the class structure used to map JSON data file of list of "Workunit" entities into Java objects. These classes provide a clear representation of the data and @@ -254,7 +344,7 @@ workunits JSON file. ![img.png](ApplicationValues.png) ![img.png](ApplicationValue.png) -**12. ActivitiesTest** +### ActivitiesTest ![img.png](ActivitiesTest.png) @@ -269,192 +359,681 @@ Activities page: - textArray: A static final String array containing expected text elements to be present on the Activities page (e.g., " Target/Wuid", "Graph"). -- navNamesArray: A static final String array containing names used to locate the navigation link WebElements on the - Activities page (e.g., "Activities", "ECL", "Files). -- tabsListMap: A static final Map that defines the expected sub-tabs for each navigation link. The key is the navigation - link name, and the value is a List of expected sub-tabs. **Methods:** 1. testActivitiesPage (Test Method) -- Obtains the WebDriver instance (WebDriverHolder.getDriver()). -- Opens the Activities page using the URL from Config.ACTIVITIES_URL and Common.openWebPage. -- Retrieves a Logger instance for logging test results (LoggerHolder.getLogger()). -- Calls testForAllText to verify the presence of expected text elements on the page. -- Calls getNavWebElements to retrieve a list of NavigationWebElement objects representing navigation links. -- Calls testForNavigationLinks to verify the functionality of navigation links. +- This is the main test method annotated with @Test to be run as a test case. +- Initializes the WebDriver instance. +- Use the Common.openWebPage method to navigate to the URL of the Activities page, retrieved from the urlMap. +- Logs the start of the tests for the "Activities" page. +- Calls testForAllText(driver) to check for the presence of predefined texts. +- Retrieves the navigation web elements by calling getNavWebElements(driver). +- Calls testForNavigationLinks(driver, navWebElements) to test the navigation links. +- Logs the completion of the tests for the "Activities" page. 2. testForAllText Method -- Iterates through the textArray. -- For each text element, calls Common.checkTextPresent to check its presence on the Activities page using the provided - WebDriver, text element, page name ("Activities Page"), and Logger instance. +- This method checks if specific texts are present on the "Activities" page. +- Logs the start of text presence tests. +- Iterates over each text in textArray. +- Calls Common.checkTextPresent(driver, text, "Activities Page") to verify the presence of each text on the page. 3. testForNavigationLinks Method -- Takes the WebDriver instance, a list of NavigationWebElement objects, and a Logger instance as arguments. -- Iterates through the list of NavigationWebElement objects: - - Clicks on the WebElement using the webElement().click() method. - - Calls testTabsForNavigationLinks to verify if the expected sub-tabs are present for the clicked link. - - Logs success/failure messages based on the verification result using the Logger instance. +- This method tests each navigation link to ensure they direct to the correct pages with the expected tabs. +- Logs the start of navigation link tests. +- Iterates over each NavigationWebElement in navWebElements. +- Clicks on each navigation element and verifies the presence of corresponding tabs by calling testTabsForNavigationLinks(driver, element). +- Logs success if all tabs are present; otherwise, logs an error with the current page details. +- Catches and logs any exceptions that occur during the process. 4. getCurrentPage Method -- Iterates through the tabsListMap. -- For each entry (navigation link name and its corresponding sub-tabs): - - Checks if all sub-tabs from the list are present in the current page source using driver.getPageSource(). - - If all sub-tabs are found, returns the navigation link name (current page). -- If no matching navigation link is found, returns "Invalid Page". +- This method determines the current page by checking the presence of specific tabs. +- Iterates over each entry in tabsListMap. +- Checks if all tabs for each page are present in the page source. +- Returns the page name if all tabs are present; otherwise, returns "Invalid Page". 5. testTabsForNavigationLinks Method: -- Takes the WebDriver instance and a NavigationWebElement object as arguments. -- Retrieves the expected sub-tabs list for the navigation element from tabsListMap. -- Gets the current page source using driver.getPageSource(). -- Iterates through the expected sub-tabs list: - - Checks if each sub-tab is present in the page source. - - If any sub-tab is missing, returns false. -- If all sub-tabs are found, returns true. +- This method verifies the presence of tabs for a given navigation element and updates the URL map with the tab URLs. +- Get the list of tabs associated with the navigation element from URLConfig.tabsListMap. +- Loop through each tab in the list. +- Use Common.waitForElement to wait for the tab element to be present in the DOM. +- Add the tab's URL to the urlMap under the corresponding navigation element. +- If a timeout exception occurs, return false. Otherwise, return true. 6. getNavWebElements Method: +- This method retrieves the web elements for the main navigation links on the Activities page. - Creates an empty list to store NavigationWebElement objects. -- Iterates through the navNamesArray. +- Iterates through the URLConfig.navNamesArray. - For each navigation link name: - Finds the WebElement using driver.findElement with By.name strategy. - Extracts the href attribute value. - Creates a new NavigationWebElement object with the name, href value, and WebElement reference. - Adds the NavigationWebElement to the list. + - Add the navigation element's URL to the urlMap. + - Log any errors that occur during the process. - Returns the list of NavigationWebElement objects. -**13. BaseTableTest** +### BaseTableTest -![img.png](BaseTableTest.png) +![img.png](BaseTableTest1.png) +![img.png](BaseTableTest2.png) -This abstract class, `BaseTableTest`, in the framework.pages package, provides a framework for -testing web pages that display tabular data. It defines methods for common functionalities like: +The BaseTableTest class is designed as a superclass for testing web pages containing tabular data. It is intended for use in automated tests, +particularly for pages like workunits, files, and queries, and includes functionality for testing their respective details pages. +It defines methods for common functionalities like: - Verifying the presence of expected text elements on the page. - Comparing the content displayed in the table with corresponding data from a JSON file. - Testing the sorting functionality of the table columns. - Verifying links within the table cells and their navigation behavior. +- Testing common details functionality, such as tab clicks. **Abstract Methods:** -1. `getPageName`: Returns the name of the page under test. -2. `getPageUrl`: Returns the URL of the page under test. -3. `getJsonFilePath`: Returns the file path of the JSON file containing reference data. -4. `getColumnNames`: Returns an array of column names displayed in the table header. -5. `getColumnKeys`: Returns an array of unique keys used to identify table headers. -6. `getUniqueKeyName`: Returns the name of the unique key used to identify table rows. -7. `getUniqueKey`: Returns the unique key value for the current row (used for logging). -8. `getColumnKeysWithLinks`: Returns an array of column keys that contain links within the table cells. -9. `parseDataUIValue`: Parses and pre-processes a data value extracted from the UI table. -10. `parseDataJSONValue`: Parses and pre-processes a data value extracted from the JSON file. -11. `parseJson`: Parses the JSON file and returns a list of objects representing the data. -12. `getColumnDataFromJson`: Extracts a specific data value from a JSON object based on the provided column key. -13. `sortJsonUsingSortOrder`: Sorts the list of JSON objects based on a given column key and sort order. -14. `getCurrentPage`: Retrieves the name of the current page displayed in the browser. +These methods must be implemented by subclasses to provide specific information and functionality for the page being tested: + +1. getPageName(): Returns the name of the page. +2. getPageUrl(): Returns the URL of the page. +3. getJsonFilePath(): Returns the file path for the JSON data file. +4. getSaveButtonDetailsPage(): Returns the text for the save button on the details page. +5. getColumnNames(): Returns an array of column names displayed in the table. +6. getDetailNames(): Returns an array of detail names for the details page. +7. getColumnKeys(): Returns an array of column keys for accessing data. +8. getDetailKeys(): Returns an array of detail keys for accessing data on the details page. +9. getCheckboxTypeForDetailsPage(): Returns the attribute type for checkboxes on the details page. +10. getAttributeTypeForDetailsPage(): Returns the attribute type for elements on the details page. +11. getAttributeValueForDetailsPage(): Returns the attribute value for elements on the details page. +12. getDetailKeysForPageLoad(): Returns an array of detail keys to check when the details page loads. +13. getUniqueKeyName(): Returns the unique key name for identifying data. +14. getUniqueKey(): Returns the unique key used for identifying data in the table. +15. getColumnKeysWithLinks(): Returns an array of column keys that contain links. +16. parseDataUIValue(dataUIValue, dataJSONValue, columnName, dataIDUIValue): Parses the UI data value. +17. parseDataJSONValue(dataJSONValue, columnName, dataIDUIValue): Parses the JSON data value. +18. parseJson(filePath): Parses the JSON file and returns a list of objects. +19. getColumnDataFromJson(object, columnKey): Gets column data from a JSON object. +20. sortJsonUsingSortOrder(currentSortOrder, columnKey): Sorts the JSON data using the specified order and column key. +21. getCurrentPage(): Returns the current page. +22. getJsonMap(): Returns a map of JSON data. +23. getColumnNamesForTabsDetailsPage(): Returns a map of column names for tabs on the details page. +24. getTabValuesForDetailsPage(): Returns an array of tab values for the details page. +25. testDetailSpecificFunctionality(name, i): Tests specific functionality on the details page. **Non-Abstract Methods:** -1. `testPage`: The main test method that orchestrates all other functionalities to test the target web page. +#### Method: `testPage()` + +This method initiates and executes a series of tests on a web page. + +1. **Open the Web Page**: The method begins by opening the specified web page using `Common.openWebPage(getPageUrl())`. + +2. **Logging**: It logs the start of tests for the page with `Common.logDebug`. + +3. **Test for All Text**: The method `testForAllText()` is called to verify the presence of expected text elements on the page. + +4. **Retrieve JSON Objects**: It retrieves all JSON objects using `getAllObjectsFromJson()` and stores them in `jsonObjects`. + +5. **Dropdown Selection**: If `jsonObjects` is not null, it determines the number of items in JSON (`numOfItemsJSON`) and uses `clickDropdown(numOfItemsJSON)` to select the appropriate dropdown value for displaying items on the page. + +6. **Content and Sorting Tests**: The method `testContentAndSortingOrder()` is called to verify the content and sorting order of items on the page. + +7. **Link Tests**: It calls `testLinksInTable()` to test all the links in the table present on the page. + +8. **Logging**: Logs the completion of tests with `Common.logDebug`. + +9. **Exception Handling**: Catches any exceptions that occur during the test and logs the error with `Common.logError`. + +#### Method: `testDetailsPage(String name, int i)` + +This method tests various aspects of a details page for a specific item. + +1. **Test Detail Page Field Names**: Depending on the configuration `Config.TEST_DETAIL_PAGE_FIELD_NAMES_ALL`, it calls `testForAllTextInDetailsPage(name)` either for all items or just the first item. + +2. **Details Content Page Test**: It calls `testDetailsContentPage(name)` to verify the content of the details page. + +3. **Detail Specific Functionality Test**: The method `testDetailSpecificFunctionality(name, i)` is called to test specific functionality on the details page. + +4. **Test Tab Clicks**: Depending on the configuration `Config.TEST_DETAIL_PAGE_TAB_CLICK_ALL`, it calls `testTabClickOnDetailsPage()` either for all items or just the first item. + +#### Method: `testTabClickOnDetailsPage()` + +This method tests the functionality of clicking through different tabs on the details page. + +1. **Logging**: Logs the start of the tab click test with `Common.logDebug`. + +2. **Wait for Page Load**: Calls `waitToLoadDetailsPage()` to ensure the details page is fully loaded. + +3. **Iterate Through Tabs**: For each tab value retrieved from `getTabValuesForDetailsPage()`: + - It tries to find the tab button element and clicks it using `javaScriptElementClick`. + - It then calls `testPresenceOfColumnNames(getColumnNamesForTabsDetailsPage().get(tabValue), tabValue)` to verify the presence of expected column names for the selected tab. + - If an error occurs, it logs the error with `Common.logError`. + +#### Method: `javaScriptElementClick(WebElement element)` + +This method clicks a web element using JavaScript. + +1. **Execute Script**: Uses a `JavascriptExecutor` to click the specified web element by executing the script `arguments[0].click();`. + +#### Method: `testPresenceOfColumnNames(List columnNames, String tabValue)` + +This method verifies the presence of specific column names within a tab on the details page. + +1. **Check Column Names**: For each column name in the provided list: + - It waits for the element containing the column name using `Common.waitForElement(By.xpath("//*[text()='" + columnName + "']"))`. + - If a `TimeoutException` occurs, it logs an error and sets a flag `allPresent` to false. + +2. **Logging**: If all column names are present, it logs success with `Common.logDetail`. If any column name is missing, it logs an error. + +#### Method: `clickOnSaveButton()` + +This method handles the action of clicking the save button on a details page. + +1. **Wait for Save Button**: It retrieves the save button element using `getSaveButtonWebElementDetailsPage()` and waits for it to be clickable with `Common.waitForElementToBeClickable`. + +2. **Click Save Button**: Clicks the save button. + +3. **Wait for Save Completion**: Waits for the save button to become disabled using `Common.waitForElementToBeDisabled`. + +#### Method: `testLinksInTable()` + +This method tests the functionality and navigation of links present in a table on the page. + +1. **Logging**: Logs the start of the link tests with `Common.logDebug`. + +2. **Iterate Through Columns with Links**: For each column key with links retrieved from `getColumnKeysWithLinks()`: + - It retrieves the data values from the UI using `getDataFromUIUsingColumnKey(columnKey)`. + - For each value, it tries to find and click the corresponding link element. + - After clicking, it checks if the page source contains the name to confirm successful navigation. + - Logs success or failure based on the result. + - It then navigates back to the original page and refreshes it. + - It verifies if the dropdown value remains unchanged after navigation. + +3. **Exception Handling**: Catches any exceptions during the link tests and logs the errors. + +#### Method: `waitToLoadDetailsPage()` + +This method ensures the details page is fully loaded by waiting for specific elements to be visible. + +1. **Initialize Wait Time**: Starts with an initial wait time specified by `Config.WAIT_TIME_IN_SECONDS`. + +2. **Check Element Visibility**: Repeatedly checks the visibility of detail keys retrieved from `getDetailKeysForPageLoad()`: + - Waits for each element by its ID using `Common.waitForElement`. + - If any element's attribute is empty, it continues waiting and increments the wait time. + +3. **Logging**: Logs the total wait time used to load the details page with `Common.logDebug`. + +#### Method: `testDetailsContentPage(String name)` + +This method tests the content of a details page for a specific item. -- Opens the target webpage using the URL obtained from `getPageUrl`. -- Calls `testForAllText` to verify the presence of expected text elements. -- Calls `testContentAndSortingOrder` to compare table data and test sorting functionality. -- Calls `testLinksInTable` to verify links within table cells and their navigation behavior. +1. **Logging**: Logs the start of the content tests with `Common.logDebug`. -2. `waitForElement`: Waits for an element to be present on the page using explicit wait with a timeout. +2. **Wait for Page Load**: Calls `waitToLoadDetailsPage()` to ensure the page is fully loaded. -3. `testContentAndSortingOrder`: +3. **Compare Content**: For each detail key: + - It retrieves the corresponding web element and its value. + - Compares the value with the expected JSON value. + - Logs errors if there are discrepancies. -- Retrieves all objects from the JSON file using `getAllObjectsFromJson`. -- Clicks on the dropdown menu to select an appropriate number of items to display in the table. -- Calls `testTableContent` to compare the table data with JSON data. -- Iterates through each column: - - Calls `testTheSortingOrderForOneColumn` to test sorting functionality for the current column. +4. **Logging**: Logs success if all values match the expected values. -4. `testTheSortingOrderForOneColumn`: +#### Method: `testForAllTextInDetailsPage(String name)` -- Gets the current sorting order for the specified column using `getCurrentSortingOrder`. -- Extracts data from the UI table and JSON file for the specified column. -- Sorts the JSON objects based on the current sorting order using `sortJsonUsingSortOrder`. -- Compares the sorted JSON data with the extracted UI data using `compareData`. +This method tests the presence of specific text elements on a details page. -5. `getCurrentSortingOrder`: Gets the current sorting order (ascending/descending/none) for a specified column. +1. **Logging**: Logs the start of the text tests with `Common.logDebug`. -6. `getDataFromJSONUsingColumnKey`: Extracts a list of data values for a specified column from all JSON objects. +2. **Check Text Presence**: For each expected text element retrieved from `getDetailNames()`: + - Calls `Common.checkTextPresent` to verify the text presence on the page. -7. `getDataFromUIUsingColumnKey`: Extracts a list of data values for a specified column from all table cells (UI). +#### Method: `testContentAndSortingOrder()` -8. `ascendingSortJson`: Sorts a list of JSON objects in ascending order based on a specified column key. +This method tests the content and sorting order of items on a page. -9. `descendingSortJson`: Sorts a list of JSON objects in descending order based on a specified column key. +1. **Logging**: Logs the start of content tests with `Common.logDebug`. -10. `testTableContent`: +2. **Test Table Content**: Calls `testTableContent()` to verify the content of the table. -- Compares the number of items displayed in the UI table with the number of objects retrieved from the JSON file. -- Iterates through each column: - - Extracts data from the UI table and JSON file for the current column. - - Calls `compareData` to compare the extracted data. +3. **Test Sorting Order**: If the content test passes, it logs the start of sorting order tests and: + - Iterates through each column key and name. + - Calls `testTheSortingOrderForOneColumn(columnKey, columnName)` to verify the sorting order. -11. `getAllObjectsFromJson`: Parses the JSON file and returns a list of objects representing the data. Handles potential - exceptions during parsing. +#### Method: `testTheSortingOrderForOneColumn(String columnKey, String columnName)` -12. `compareData`: Compares a list of data values extracted from the UI table with the corresponding list from the JSON - file. +This method tests the sorting order of a specific column. -- Iterates through each data pair: - - Calls `parseDataUIValue` and `parseDataJSONValue` to pre-process the data (if needed). - - Calls `checkValues` to perform the actual comparison and handle mismatches. +1. **Retrieve Sorting Order**: Calls `getCurrentSortingOrder(columnKey)` to retrieve the current sorting order for the column. -13. `checkValues`: Compares two data values and logs an error message if they don't match. +2. **Compare Data**: If a sorting order is retrieved, it: + - Retrieves data from the UI and JSON. + - Sorts JSON data using the retrieved sorting order. + - Compares the UI and JSON data. + - Logs success if the data is correctly sorted; otherwise, logs an error. -**14. ECLWorkUnitsTest** +#### Method: `getCurrentSortingOrder(String columnKey)` + +This method retrieves the current sorting order of a column. + +1. **Find Column Header**: Locates the column header element for the specified column key. + +2. **Retrieve Sort Order**: Retrieves the current sorting order from the column header's attribute `aria-sort`. + +3. **Click to Change Sort Order**: Clicks the column header to change the sort order and waits for the sorting order to change using `waitToLoadChangedSortOrder`. + +4. **Return New Sort Order**: Returns the new sorting order. + +#### Method: `waitToLoadChangedSortOrder(String oldSortOrder, String columnKey)` + +This method waits for the sorting order to change after clicking a column header. + +1. **Initialize Wait Time**: Starts with an initial wait time specified by `Config.WAIT_TIME_IN_SECONDS`. + +2. **Check Sort Order Change**: Repeatedly checks the sorting order of the column header until it changes from the old sort order. + +3. **Return New Sort Order**: Returns the new sorting order once it has changed. + +#### Method: `getDataFromJSONUsingColumnKey(String columnKey)` + +This method retrieves data from JSON objects for a specified column key. + +1. **Iterate Through JSON Objects**: For each JSON object: + - Retrieves the column data using `getColumnDataFromJson`. + +2. **Return Data**: Returns the list of column data. + +#### Method: `getDataFromUIUsingColumnKey(String columnKey)` + +This method retrieves data from the UI based on a specified column key. + +1. **Initialize List**: Starts by creating an empty list to store the data. +2. **Load UI Elements**: Calls the `waitToLoadListOfAllUIObjects` method to get all web elements corresponding to the column key. +3. **Extract Text**: Iterates through the list of web elements (excluding the header) and adds the text content of each element to the list. +4. **Error Handling**: If any exception occurs, it logs an error message. +5. **Return Data**: Finally, returns the list of extracted data. + +#### Method: `waitToLoadListOfAllUIObjects(String columnKey)` + +This method waits until all UI elements corresponding to a column key are loaded. + +1. **Initialize Wait Time**: Starts with an initial wait time. +2. **Find Elements**: Uses a loop to repeatedly find web elements matching the column key's XPath. +3. **Check Element Count**: Checks if the number of elements (excluding the header) matches the number of JSON objects. +4. **Wait and Retry**: If the elements are not fully loaded, it sleeps for the current wait time, increments the wait time, and retries. +5. **Return Elements**: Once the elements are fully loaded, returns the list of web elements. + +#### Method: `ascendingSortJson(String columnKey)` + +This method sorts the JSON objects in ascending order based on a column key. + +1. **Sort JSON Objects**: Uses a comparator to sort the list of JSON objects by comparing the values of the specified column key. +2. **Error Handling**: If any exception occurs during sorting, logs an error message. + +#### Method: `descendingSortJson(String columnKey)` + +This method sorts the JSON objects in descending order based on a column key. + +1. **Sort JSON Objects**: Similar to `ascendingSortJson`, but uses a reversed comparator to sort the list in descending order. +2. **Error Handling**: If any exception occurs during sorting, logs an error message. + +#### Method: `testTableContent()` + +This method tests the content of a table by comparing the UI data with JSON data. + +1. **Log Number of Objects**: Logs the number of objects retrieved from JSON. +2. **Retrieve UI Data**: Calls `getDataFromUIUsingColumnKey` to get the data from the UI for the unique key. +3. **Compare Counts**: Compares the number of objects from JSON and UI. Logs an error if they are not equal and returns false. +4. **Initialize Pass Flag**: Initializes a flag to track if all comparisons pass. +5. **Iterate Through Columns**: For each column key: + - Retrieves data from UI and JSON. + - Calls `compareData` to compare the data and updates the pass flag based on the result. +6. **Return Result**: Returns the pass flag indicating whether all comparisons passed. + +#### Method: `getAllObjectsFromJson()` + +This method retrieves all objects from a JSON file. + +1. **Get File Path**: Retrieves the path of the JSON file. +2. **Parse JSON**: Calls `parseJson` to parse the JSON file and return a list of objects. +3. **Error Handling**: If any exception occurs during parsing, logs an error message. +4. **Return Data**: Returns the list of parsed objects. + +#### Method: `compareData(List dataUI, List dataJSON, List dataIDUI, String columnName)` + +This method compares the data from UI and JSON for a specific column. + +1. **Initialize Pass Flag**: Initializes a flag to track if all comparisons pass. +2. **Iterate Through Data**: For each item in the data lists: + - Parses the UI and JSON values. + - Calls `checkValues` to compare the values and updates the pass flag based on the result. +3. **Log Success**: If all comparisons pass, logs a success message. +4. **Return Result**: Returns the pass flag indicating whether all comparisons passed. + +#### Method: `checkValues(Object dataUIValue, Object dataJSONValue, Object dataIDUIValue, String columnName)` + +This method checks if the UI value matches the JSON value for a specific column. + +1. **Compare Values**: Compares the UI value with the JSON value. +2. **Log Error**: If the values do not match, logs an error message. +3. **Return Result**: Returns a boolean indicating whether the values match. + +#### Method: `clickDropdown(int numOfItemsJSON)` + +This method selects a dropdown value based on the number of JSON items. + +1. **Find Dropdown**: Locates and clicks the dropdown element. +2. **Wait for Dropdown List**: Waits for the dropdown list to become visible. +3. **Determine Selected Value**: Determines the smallest dropdown value greater than the number of JSON items. +4. **Select Option**: Iterates through the options and clicks the one matching the selected value. +5. **Wait for Invisibility**: Waits for the dropdown list to become invisible. +6. **Log and Refresh**: Logs the selected value and refreshes the page. +7. **Error Handling**: If any exception occurs, logs an error message. + +#### Method: `getSelectedDropdownValue()` + +This method retrieves the currently selected value of a dropdown. + +1. **Find Dropdown**: Locates the dropdown element. +2. **Retrieve Text**: Gets and trims the text of the dropdown element. +3. **Error Handling**: If any exception occurs, logs an error message. +4. **Return Value**: Returns the retrieved text. + +#### Method: `getSaveButtonWebElementDetailsPage()` + +This method retrieves the web element for the save button on the details page. + +1. **Locate Save Button**: Uses an XPath to find the save button element. +2. **Return Element**: Returns the located web element. + +#### Method: `testForAllText()` + +This method tests the presence of specific text elements on a page. + +1. **Log Start**: Logs the start of the text tests. +2. **Check Text**: For each expected text element: + - Calls `Common.checkTextPresent` to verify the text presence on the page. + + + +### ECLWorkUnitsTest ![img.png](ECLWorkUnitsTest.png) This class, `ECLWorkUnitsTest`, in the framework.pages package, extends the `BaseTableTest` class and specifically implements test cases for the ECL Workunits page within the ECL Watch UI. It inherits functionalities for common table testing procedures and specializes them for the -ECL Workunits data and behavior. - -**Override Methods:** - -1. `getPageName`: Returns the display name of the page under test ("ECL Workunits"). -2. `getPageUrl`: Returns the URL for the ECL Workunits page constructed using `Config.ECL_WORK_UNITS_URL`. -3. `getJsonFilePath`: Determines the file path of the JSON file containing ECL Workunit data based on the test execution - environment (local vs. GitHub Actions). -4. `getColumnNames`: Returns an array of column names displayed in the ECL Workunits table header. -5. `getColumnKeys`: Returns an array of unique keys used to identify ECL Workunit table headers. -6. `getColumnKeysWithLinks`: Returns an array specifying the column key that contains links within table cells ( - currently, only "Wuid" has links). -7. `getUniqueKeyName`: Returns the name of the unique key used to identify ECL Workunit table rows ("WUID"). -8. `getUniqueKey`: Returns the actual value of the unique key for the current row (used for logging). -9. `parseJson`: Parses the JSON file specific to ECL Workunits and returns a list of `ECLWorkunit` objects representing - the data. -10. `getColumnDataFromJson`: Extracts a specific data value from an `ECLWorkunit` object based on the provided column - key. -11. `parseDataUIValue`: Parses and pre-processes a data value extracted from the ECL Workunits UI table. -12. `parseDataJSONValue`: Parses and pre-processes a data value extracted from the ECL Workunits JSON file (if - necessary). -13. `sortJsonUsingSortOrder`: Sorts the list of `ECLWorkunit` objects based on a given column key and sort order, - considering the specific sorting behavior for the ECL Workunits table (default sort is descending by WUID). -14. `getCurrentPage`: Retrieves the title attribute of the "wuid" element to determine the current page displayed. - -**Additional Methods:** - -1. `getCostColumns`: Returns a list of column names that represent cost-related data (Compile Cost, Execution Cost, File - Access Cost). - -**Test Method:** - -1. `testingECLWorkUnitsPage`: Calls the inherited `testPage` method to execute the core test logic for the ECL Workunits - page. - -**Class Relationship Structure:** +ECL Workunits data and behavior. It includes test cases for both the main page and the details page of the workunits. + +Here is the detailed explanation and implementation steps of the given methods without code snippets: + +#### Method: `testingECLWorkUnitsPage` +This method is a test method annotated with `@Test`, which indicates that it is a test case to be run using a testing framework like JUnit or TestNG. The method calls `testPage()` to execute the test logic for the ECL Work Units page. + +1. Annotate the method with `@Test` to indicate that it is a test case. +2. Within the method, call the `testPage()` method to perform the necessary testing actions on the ECL Work Units page. + +#### Method: `getPageName` +This method returns the name of the page being tested, which in this case is "ECL Workunits". + +1. Override the method to return the string "ECL Workunits". + +#### Method: `getPageUrl` +The getPageUrl method is designed to retrieve the URL of a specific page within the application. It overrides a method from a superclass, BaseTableTest, and provides the URL for the "Workunits" tab under the "ECL" navigation section. This URL is fetched from a pre-configured map of URLs. + +1. The method accesses the urlMap from the URLConfig class, which stores URL mappings for different navigation sections and their tabs. +2. The method fetches the URLMapping object for the "ECL" navigation section from the urlMap using URLConfig.NAV_ECL as the key. +3. From the URLMapping object of the "ECL" navigation section, the method retrieves the nested URL mapping for the "Workunits" tab using URLConfig.TAB_ECL_WORKUNITS as the key. +4. Finally, the method returns the URL associated with the "Workunits" tab. + +#### Method: `getJsonFilePath` +This method returns the file path of the JSON file containing Workunits test data. +Combine the directory path (Config.PATH_FOLDER_JSON) with the filename (Config.WORKUNITS_JSON_FILE_NAME) to form the complete file path. + +1. Override the method to return the local path `Config.PATH_LOCAL_WORKUNITS_JSON` if the test is running locally. +2. Return the CI/CD path `Config.PATH_GH_ACTION_WORKUNITS_JSON` otherwise. + +#### Method: `getColumnNames` +This method returns an array of column names to be displayed on the ECL Work Units page. + +1. Override the method to return an array of column names: "WUID", "Owner", "Job Name", "Cluster", "State", "Total Cluster Time", "Compile Cost", "Execution Cost", "File Access Cost". + +#### Method: `getColumnKeys` +This method returns an array of column keys corresponding to the column names used in the JSON data. + +1. Override the method to return an array of column keys: "Wuid", "Owner", "Jobname", "Cluster", "State", "TotalClusterTime", "Compile Cost", "Execution Cost", "File Access Cost". + +#### Method: `getSaveButtonDetailsPage` +This method returns the label of the save button on the details page. + +1. Override the method to return the string "Save". + +#### Method: `getDetailNames` +This method returns an array of detail names to be displayed on the work unit details page. + +1. Override the method to return an array of detail names: "WUID", "Action", "State", "Owner", "Job Name", "Description", "Potential Savings", "Compile Cost", "Execution Cost", "File Access Cost", "Protected", "Cluster", "Total Cluster Time", "Aborted by", "Aborted time", "Services". + +#### Method: `getDetailKeys` +This method returns an array of detail keys corresponding to the detail names used in the JSON data. + +1. Override the method to wait for the element with ID "state" to load. +2. Check if the state value is in the list of bad states (`compiled`, `failed`). +3. If the state is in the bad states list, return a subset of detail keys: "wuid", "action", "state", "owner", "jobname", "cluster". +4. Otherwise, return the full array of detail keys: "wuid", "action", "state", "owner", "jobname", "compileCost", "executeCost", "fileAccessCost", "protected", "cluster", "totalClusterTime". + +#### Method: `getDetailKeysForPageLoad` +This method returns an array of keys to be used for loading the details page. + +1. Override the method to return an array of keys: "wuid", "state", "jobname", "cluster". + +#### Method: `getCheckboxTypeForDetailsPage` +This method returns the type of the checkbox used on the details page. + +1. Override the method to return the string "checkbox". + +#### Method: `getAttributeTypeForDetailsPage` +This method returns the attribute type used to identify elements on the details page. + +1. Override the method to return the string "type". + +#### Method: `getAttributeValueForDetailsPage` +This method returns the attribute value used to identify elements on the details page. + +1. Override the method to return the string "value". + +#### Method: `getColumnKeysWithLinks` +This method returns an array of column keys that contain links. + +1. Override the method to return an array with a single key: "Wuid". + +#### Method: `getUniqueKeyName` +This method returns the name of the unique key used to identify work units. + +1. Override the method to return the string "WUID". + +#### Method: `getUniqueKey` +This method returns the key used to uniquely identify work units. + +1. Override the method to return the string "Wuid". + +#### Method: `getTabValuesForDetailsPage` +This method returns an array of tab values for the details page. + +1. Define a method to return an array of tab values: "variables", "outputs", "inputs", "metrics", "workflows", "queries", "resources", "helpers", "xml". + +#### Method: `getColumnNamesForTabsDetailsPage` +This method returns a map where each key is the value attribute of an HTML element of a tab, and the value is a list of column names for that tab. + +1. Override the method to return a map of tab values and their corresponding column names: + - "variables": ["Type", "Name", "Value"] + - "outputs": ["Name", "File Name", "Value", "Views"] + - "inputs": ["Name", "File Cluster", "Usage"] + - "metrics": ["Refresh", "Hot spots", "Timeline", "Options"] - there is no column in this tab, so checking the presence of these buttons + - "workflows": ["Name", "Subtype", "Count", "Remaining"] + - "queries": ["ID", "Priority", "Name", "Target", "WUID", "Dll", "Published By", "Status"] + - "resources": ["Name", "Refresh", "Open", "Preview"] - Preview is not a column, it is a button, keeping it for additional check for Resources tab + - "helpers": ["Type", "Description", "File Size"] + - "xml": [" urlMappings; + + public URLMapping(String name, String url) { + this.name = name; + this.url = url; + urlMappings = new HashMap<>(); + } + + public String getUrl() { + return url; + } + + public HashMap getUrlMappings() { + return urlMappings; + } + + @Override + public String toString() { + return "URLMapping{" + + "name='" + name + '\'' + + ", url='" + url + '\'' + + ", urlMappings=" + urlMappings + + '}'; + } +} diff --git a/esp/src/test-ui/tests/framework/pages/ActivitiesTest.java b/esp/src/test-ui/tests/framework/pages/ActivitiesTest.java index 50b0ac82213..41dc5dc6ff4 100644 --- a/esp/src/test-ui/tests/framework/pages/ActivitiesTest.java +++ b/esp/src/test-ui/tests/framework/pages/ActivitiesTest.java @@ -1,72 +1,70 @@ package framework.pages; -import framework.config.Config; +import framework.config.URLConfig; import framework.model.NavigationWebElement; -import framework.setup.LoggerHolder; -import framework.setup.WebDriverHolder; +import framework.model.URLMapping; import framework.utility.Common; import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; +import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebElement; import org.testng.annotations.Test; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.logging.Logger; + +import static framework.config.URLConfig.urlMap; public class ActivitiesTest { static final String[] textArray = {"Target/Wuid", "Graph", "State", "Owner", "Job Name"}; - static final String[] navNamesArray = {"Activities", "ECL", "Files", "Published Queries", "Operations"}; - static final Map> tabsListMap = Map.of( - "Activities", List.of("Activities", "Event Scheduler"), - "ECL", List.of("Workunits", "Playground"), - "Files", List.of("Logical Files", "Landing Zones", "Workunits", "XRef (L)"), - "Published Queries", List.of("Queries", "Package Maps"), - "Operations", List.of("Topology (L)", "Disk Usage (L)", "Target Clusters (L)", "Cluster Processes (L)", "System Servers (L)", "Security (L)", "Dynamic ESDL (L)") - //"DummyNavName", List.of("DummyTab1", "DummyTab2") - ); @Test public void testActivitiesPage() { - WebDriver driver = WebDriverHolder.getDriver(); - Common.openWebPage(driver, Common.getUrl(Config.ACTIVITIES_URL)); + Common.openWebPage(urlMap.get(URLConfig.NAV_ACTIVITIES).getUrl()); + + Common.logDebug("Tests started for: Activities page."); - Logger logger = LoggerHolder.getLogger(); + testForAllText(); - testForAllText(logger, driver); + List navWebElements = getNavWebElements(); - List navWebElements = getNavWebElements(driver); + testForNavigationLinks(navWebElements); - testForNavigationLinks(driver, navWebElements, logger); + Common.logDebug("Tests finished for: Activities page."); + Common.logDebug("URL Map Generated: " + urlMap); } - private void testForNavigationLinks(WebDriver driver, List navWebElements, Logger logger) { + private void testForNavigationLinks(List navWebElements) { + + Common.logDebug("Tests started for: Activities page: Testing Navigation Links"); for (NavigationWebElement element : navWebElements) { - element.webElement().click(); - - if (testTabsForNavigationLinks(driver, element)) { - String msg = "Success: Navigation Menu Link for " + element.name() + ". URL : " + element.hrefValue(); - System.out.println(msg); - } else { - String currentPage = getCurrentPage(driver); - String errorMsg = "Failure: Navigation Menu Link for " + element.name() + " page failed. The current navigation page that we landed on is " + currentPage + ". Current URL : " + element.hrefValue(); - System.err.println(errorMsg); - logger.severe(errorMsg); + + try { + element.webElement().click(); + + if (testTabsForNavigationLinks(element)) { + String msg = "Success: Navigation Menu Link for " + element.name() + ". URL : " + element.hrefValue(); + Common.logDetail(msg); + } else { + String currentPage = getCurrentPage(); + String errorMsg = "Failure: Navigation Menu Link for " + element.name() + " page failed. The current navigation page that we landed on is " + currentPage + ". Current URL : " + element.hrefValue(); + Common.logError(errorMsg); + } + } catch (Exception ex) { + Common.logError("Failure: Exception in Navigation Link for " + element.name() + ". URL : " + element.hrefValue() + " Error: " + ex.getMessage()); } } } - private String getCurrentPage(WebDriver driver) { + private String getCurrentPage() { - for (var entry : tabsListMap.entrySet()) { + for (var entry : URLConfig.tabsListMap.entrySet()) { List tabs = entry.getValue(); boolean allTabsPresent = true; for (String tab : tabs) { - if (!driver.getPageSource().contains(tab)) { + if (!Common.driver.getPageSource().contains(tab)) { allTabsPresent = false; break; } @@ -80,12 +78,14 @@ private String getCurrentPage(WebDriver driver) { return "Invalid Page"; } - private boolean testTabsForNavigationLinks(WebDriver driver, NavigationWebElement element) { - List tabsList = tabsListMap.get(element.name()); - String pageSource = driver.getPageSource(); + private boolean testTabsForNavigationLinks(NavigationWebElement element) { + List tabsList = URLConfig.tabsListMap.get(element.name()); for (String tab : tabsList) { - if (!pageSource.contains(tab)) { + try { + WebElement webElement = Common.waitForElement(By.xpath("//a[text()='" + tab + "']")); + urlMap.get(element.name()).getUrlMappings().put(tab, new URLMapping(tab, webElement.getAttribute("href"))); + } catch (TimeoutException ex) { return false; } } @@ -93,25 +93,29 @@ private boolean testTabsForNavigationLinks(WebDriver driver, NavigationWebElemen return true; } - private List getNavWebElements(WebDriver driver) { + private List getNavWebElements() { List navWebElements = new ArrayList<>(); - for (String navName : navNamesArray) { - WebElement webElement = driver.findElement(By.name(navName)).findElement(By.tagName("a")); - String hrefValue = webElement.getAttribute("href"); - navWebElements.add(new NavigationWebElement(navName, hrefValue, webElement)); - } + for (String navName : URLConfig.navNamesArray) { + try { + WebElement webElement = Common.driver.findElement(By.name(navName)).findElement(By.tagName("a")); + String hrefValue = webElement.getAttribute("href"); + navWebElements.add(new NavigationWebElement(navName, hrefValue, webElement)); - //navWebElements.add(new NavigationWebElement("DummyNavName", "http://192.168.0.221:8010/esp/files/index.html#/files", navWebElements.get(2).webElement())); + urlMap.put(navName, new URLMapping(navName, hrefValue)); + } catch (Exception ex) { + Common.logError("Failure: Activities Page for Navigation Element: " + navName + ": Error: " + ex.getMessage()); + } + } return navWebElements; } - private void testForAllText(Logger logger, WebDriver driver) { - + private void testForAllText() { + Common.logDebug("Tests started for: Activities page: Testing Text"); for (String text : textArray) { - Common.checkTextPresent(driver, text, "Activities Page", logger); + Common.checkTextPresent(text, "Activities Page"); } } } diff --git a/esp/src/test-ui/tests/framework/pages/BaseTableTest.java b/esp/src/test-ui/tests/framework/pages/BaseTableTest.java index 6468196aff5..20121314bb7 100644 --- a/esp/src/test-ui/tests/framework/pages/BaseTableTest.java +++ b/esp/src/test-ui/tests/framework/pages/BaseTableTest.java @@ -1,21 +1,20 @@ package framework.pages; import framework.config.Config; -import framework.setup.LoggerHolder; -import framework.setup.WebDriverHolder; import framework.utility.Common; import org.openqa.selenium.By; -import org.openqa.selenium.WebDriver; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.TimeoutException; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import java.time.Duration; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; +import java.util.*; import java.util.function.Function; -import java.util.logging.Logger; + + +// This class is the super class for all the those web pages that contains a tabular data for e.g. workunits, files, queries etc. It also contains the tests for their respective details page including testing text, content and tabs. public abstract class BaseTableTest { @@ -25,139 +24,360 @@ public abstract class BaseTableTest { protected abstract String getJsonFilePath(); + protected abstract String getSaveButtonDetailsPage(); + protected abstract String[] getColumnNames(); + protected abstract String[] getDetailNames(); + protected abstract String[] getColumnKeys(); + protected abstract String[] getDetailKeys(); + + protected abstract String getCheckboxTypeForDetailsPage(); + + protected abstract String getAttributeTypeForDetailsPage(); + + protected abstract String getAttributeValueForDetailsPage(); + + protected abstract String[] getDetailKeysForPageLoad(); + protected abstract String getUniqueKeyName(); protected abstract String getUniqueKey(); protected abstract String[] getColumnKeysWithLinks(); - protected abstract Object parseDataUIValue(Object dataUIValue, String columnName, Object dataIDUIValue, Logger logger); + protected abstract Object parseDataUIValue(Object dataUIValue, Object dataJSONValue, String columnName, Object dataIDUIValue); - protected abstract Object parseDataJSONValue(Object dataJSONValue, String columnName, Object dataIDUIValue, Logger logger); + protected abstract Object parseDataJSONValue(Object dataJSONValue, String columnName, Object dataIDUIValue); protected abstract List parseJson(String filePath) throws Exception; protected abstract Object getColumnDataFromJson(T object, String columnKey); - protected abstract void sortJsonUsingSortOrder(String currentSortOrder, List jsonObjects, String columnKey); + protected abstract void sortJsonUsingSortOrder(String currentSortOrder, String columnKey); + + protected abstract String getCurrentPage(); + + protected abstract Map getJsonMap(); - protected abstract String getCurrentPage(WebDriver driver); + protected abstract Map> getColumnNamesForTabsDetailsPage(); + + protected abstract String[] getTabValuesForDetailsPage(); + + protected abstract void testDetailSpecificFunctionality(String name, int i); + + protected List jsonObjects; protected void testPage() { + Common.openWebPage(getPageUrl()); + try { - WebDriver driver = WebDriverHolder.getDriver(); - Common.openWebPage(driver, getPageUrl()); - Logger logger = LoggerHolder.getLogger(); + Common.logDebug("Tests started for: " + getPageName() + " page."); + + testForAllText(); + + jsonObjects = getAllObjectsFromJson(); + if (jsonObjects != null) { + + int numOfItemsJSON = jsonObjects.size(); + clickDropdown(numOfItemsJSON); + + testContentAndSortingOrder(); + } + + testLinksInTable(); - testForAllText(driver, logger); - testContentAndSortingOrder(driver, logger); - testLinksInTable(driver, logger); + Common.logDebug("Tests finished for: " + getPageName() + " page."); } catch (Exception ex) { - System.out.println(ex.getMessage()); + Common.logError(ex.getMessage()); + } + } + + private void testDetailsPage(String name, int i) { + + if (!Config.TEST_DETAIL_PAGE_FIELD_NAMES_ALL && i == 0) { // TEST_DETAIL_PAGE_FIELD_NAMES_ALL = true means the test will run for all items and false means it will only run for the first item + testForAllTextInDetailsPage(name); + } else if (Config.TEST_DETAIL_PAGE_FIELD_NAMES_ALL) { + testForAllTextInDetailsPage(name); + } + + testDetailsContentPage(name); + testDetailSpecificFunctionality(name, i); + + if (!Config.TEST_DETAIL_PAGE_TAB_CLICK_ALL && i == 0) { // TEST_DETAIL_PAGE_TAB_CLICK_ALL = true means the test will run for all items and false means it will only run for the first item + testTabClickOnDetailsPage(); + } else if (Config.TEST_DETAIL_PAGE_TAB_CLICK_ALL) { + testTabClickOnDetailsPage(); + } + } + + private void testTabClickOnDetailsPage() { + + Common.logDebug("Test started for: Tab Click on " + getPageName() + " Details Page."); + + waitToLoadDetailsPage(); + + for (var tabValue : getTabValuesForDetailsPage()) { + + try { + WebElement element = Common.driver.findElement(By.xpath("//button[@value='" + tabValue + "']")); + javaScriptElementClick(element); + testPresenceOfColumnNames(getColumnNamesForTabsDetailsPage().get(tabValue), tabValue); + } catch (Exception ex) { + Common.logError("Error: " + getPageName() + " Details Page. Testing tab click on: " + tabValue + " Error: " + ex.getMessage()); + } + } + + } + + protected void javaScriptElementClick(WebElement element) { + + JavascriptExecutor js = (JavascriptExecutor) Common.driver; + js.executeScript("arguments[0].click();", element); + } + + private void testPresenceOfColumnNames(List columnNames, String tabValue) { + + boolean allPresent = true; + String currentURL = Common.driver.getCurrentUrl(); + for (String columnName : columnNames) { + + try { + Common.waitForElement(By.xpath("//*[text()='" + columnName + "']")); + } catch (TimeoutException ex) { + Common.logError("Failure: " + getPageName() + " Details Page. Text : " + columnName + " not present for tab: " + tabValue + ". Current URL: " + currentURL); + allPresent = false; + } } + + if (allPresent) { + Common.logDetail("Success: " + getPageName() + " Details Page. Tab click on: " + tabValue); + } + } + + protected void clickOnSaveButton() { + + WebElement saveButton = getSaveButtonWebElementDetailsPage(); + Common.waitForElementToBeClickable(saveButton); + saveButton.click(); + + saveButton = getSaveButtonWebElementDetailsPage(); + Common.waitForElementToBeDisabled(saveButton); } - private void testLinksInTable(WebDriver driver, Logger logger) { + private void testLinksInTable() { + + Common.logDebug("Tests started for: " + getPageName() + " page: Testing Links"); for (String columnKey : getColumnKeysWithLinks()) { - List values = getDataFromUIUsingColumnKey(driver, columnKey); + List values = getDataFromUIUsingColumnKey(columnKey); + + for (int i = 0; i < values.size(); i++) { + + try { + String name = values.get(i).toString().trim(); + + WebElement element = Common.waitForElement(By.xpath("//div[text()='" + name + "']/..")); + String href = Common.driver.getCurrentUrl(); + + String dropdownValueBefore = getSelectedDropdownValue(); - int i = 1; + element.click(); - for (Object value : values) { - String name = value.toString().trim(); - //WebElement element = driver.findElement(By.xpath("//div[contains(text(), '"+name+"')]/..")); - WebElement element = waitForElement(driver, By.xpath("//div[contains(text(), '" + name + "')]/..")); - String href = element.findElement(By.tagName("a")).getAttribute("href"); + if (Common.driver.getPageSource().contains(name)) { + String msg = "Success: " + getPageName() + ": Link Test Pass for " + (i + 1) + ". " + name + ". URL : " + href; + Common.logDetail(msg); + // after the link test has passed, the code tests the details page(including, the text, content and tabs) it has landed on + testDetailsPage(name, i); - String dropdownValueBefore = getSelectedDropdownValue(driver); + } else { + String currentPage = getCurrentPage(); + String errorMsg = "Failure: " + getPageName() + ": Link Test Fail for " + (i + 1) + ". " + name + " page failed. The current navigation page that we landed on is " + currentPage + ". Current URL : " + href; + Common.logError(errorMsg); + } - element.click(); + Common.driver.navigate().to(getPageUrl()); + Common.driver.navigate().refresh(); - if (driver.getPageSource().contains(name)) { - String msg = "Success: " + getPageName() + ": Link Test Pass for " + i++ + ". " + name + ". URL : " + href; - System.out.println(msg); + String dropdownValueAfter = getSelectedDropdownValue(); + + // Log error if the dropdown value has changed + if (!dropdownValueBefore.equals(dropdownValueAfter)) { + String dropdownErrorMsg = "Failure: " + getPageName() + ": Dropdown value changed after navigating back. Before: " + dropdownValueBefore + ", After: " + dropdownValueAfter; + Common.logError(dropdownErrorMsg); + } + } catch (Exception ex) { + Common.logError("Failure: " + getPageName() + ": Exception in testing links for column: " + columnKey + " value: " + values.get(i) + " Error: " + ex.getMessage()); + } + } + } + } + + protected void waitToLoadDetailsPage() { + + int waitTimeInSecs = Config.WAIT_TIME_IN_SECONDS; + + boolean allVisible; + + do { + Common.sleepWithTime(waitTimeInSecs); + allVisible = true; + + for (String detailKey : getDetailKeysForPageLoad()) { + WebElement element = Common.waitForElement(By.id(detailKey)); + if ("".equals(element.getAttribute(getAttributeValueForDetailsPage()))) { + allVisible = false; + break; + } + } + + if (allVisible) { + break; + } + + waitTimeInSecs++; + + } while (waitTimeInSecs < Config.WAIT_TIME_THRESHOLD_IN_SECONDS); + + Common.logDebug("Sleep Time In Seconds To Load Details Page: " + waitTimeInSecs); + } + + private void testDetailsContentPage(String name) { + Common.logDebug("Tests started for : " + getPageName() + " Details page content: " + getUniqueKeyName() + " : " + name); + try { + waitToLoadDetailsPage(); // sleep until the specific detail fields have loaded + } catch (Exception ex) { + Common.logError("Error: " + getPageName() + ": Exception in waitToLoadDetailsPage: " + getUniqueKeyName() + " : " + name + " Exception: " + ex.getMessage()); + } + + boolean pass = true; + + T object = getJsonMap().get(name); + + for (String detailKey : getDetailKeys()) { + try { + WebElement element = Common.waitForElement(By.id(detailKey)); + + Object dataUIValue, dataJSONValue; + + if (element.getAttribute(getAttributeTypeForDetailsPage()).equals(getCheckboxTypeForDetailsPage())) { + dataUIValue = element.isSelected(); } else { - String currentPage = getCurrentPage(driver); - String errorMsg = "Failure: " + getPageName() + ": Link Test Fail for " + i++ + ". " + name + " page failed. The current navigation page that we landed on is " + currentPage + ". Current URL : " + href; - System.err.println(errorMsg); - logger.severe(errorMsg); + dataUIValue = element.getAttribute(getAttributeValueForDetailsPage()); } - driver.navigate().to(getPageUrl()); - driver.navigate().refresh(); + dataJSONValue = getColumnDataFromJson(object, detailKey); - String dropdownValueAfter = getSelectedDropdownValue(driver); + dataUIValue = parseDataUIValue(dataUIValue, dataJSONValue, detailKey, name); + dataJSONValue = parseDataJSONValue(dataJSONValue, detailKey, name); - // Log error if the dropdown value has changed - if (!dropdownValueBefore.equals(dropdownValueAfter)) { - String dropdownErrorMsg = "Failure: " + getPageName() + ": Dropdown value changed after navigating back. Before: " + dropdownValueBefore + ", After: " + dropdownValueAfter; - System.err.println(dropdownErrorMsg); - logger.severe(dropdownErrorMsg); + if (!dataUIValue.equals(dataJSONValue)) { + Common.logError("Failure: " + getPageName() + " Details page, Incorrect " + detailKey + " : " + dataUIValue + " in UI for " + getUniqueKeyName() + " : " + name + ". Correct " + detailKey + " is: " + dataJSONValue); + pass = false; } + + } catch (Exception ex) { + Common.logError("Error: Details " + getPageName() + "Page, for: " + getUniqueKeyName() + " : " + name + " Error: " + ex.getMessage()); } } + + if (pass) { + Common.logDetail("Success: " + getPageName() + " Details page: All values test passed for: " + getUniqueKeyName() + " : " + name); + } } - protected WebElement waitForElement(WebDriver driver, By locator) { - return new WebDriverWait(driver, Duration.ofSeconds(10)).until(ExpectedConditions.presenceOfElementLocated(locator)); + private void testForAllTextInDetailsPage(String name) { + Common.logDebug("Tests started for: " + getPageName() + " Details page: " + getUniqueKeyName() + ": " + name + " Testing Text"); + for (String text : getDetailNames()) { + Common.checkTextPresent(text, getPageName() + " Details page: " + getUniqueKeyName() + ": " + name); + } } - private void testContentAndSortingOrder(WebDriver driver, Logger logger) { - List jsonObjects = getAllObjectsFromJson(logger); + private void testContentAndSortingOrder() { - if (jsonObjects != null) { - int numOfItemsJSON = jsonObjects.size(); - clickDropdown(driver, numOfItemsJSON); + Common.logDebug("Tests started for: " + getPageName() + " page: Testing Content"); - if (testTableContent(driver, logger, jsonObjects)) { - for (int i = 0; i < getColumnKeys().length; i++) { - testTheSortingOrderForOneColumn(driver, logger, jsonObjects, getColumnKeys()[i], getColumnNames()[i]); - } + if (testTableContent()) { + + Common.logDebug("Tests started for: " + getPageName() + " page: Testing Sorting Order"); + + for (int i = 0; i < getColumnKeys().length; i++) { + testTheSortingOrderForOneColumn(getColumnKeys()[i], getColumnNames()[i]); } } } - private void testTheSortingOrderForOneColumn(WebDriver driver, Logger logger, List jsonObjects, String columnKey, String columnName) { + private void testTheSortingOrderForOneColumn(String columnKey, String columnName) { for (int i = 0; i < 3; i++) { - String currentSortOrder = getCurrentSortingOrder(driver, columnKey); - List columnDataFromUI = getDataFromUIUsingColumnKey(driver, columnKey); + String currentSortOrder = getCurrentSortingOrder(columnKey); + if (currentSortOrder != null) { + List columnDataFromUI = getDataFromUIUsingColumnKey(columnKey); - sortJsonUsingSortOrder(currentSortOrder, jsonObjects, columnKey); + sortJsonUsingSortOrder(currentSortOrder, columnKey); - List columnDataFromJSON = getDataFromJSONUsingColumnKey(columnKey, jsonObjects); - List columnDataIDFromUI = getDataFromUIUsingColumnKey(driver, getUniqueKey()); + List columnDataFromJSON = getDataFromJSONUsingColumnKey(columnKey); + List columnDataIDFromUI = getDataFromUIUsingColumnKey(getUniqueKey()); - if (compareData(columnDataFromUI, columnDataFromJSON, columnDataIDFromUI, logger, columnName)) { - System.out.println("Success: " + getPageName() + ": Values are correctly sorted in " + currentSortOrder + " order by: " + columnName); + if (compareData(columnDataFromUI, columnDataFromJSON, columnDataIDFromUI, columnName)) { + Common.logDebug("Success: " + getPageName() + ": Values are correctly sorted in " + currentSortOrder + " order by: " + columnName); + } else { + String errMsg = "Failure: " + getPageName() + ": Values are not correctly sorted in " + currentSortOrder + " order by: " + columnName; + Common.logError(errMsg); + } } else { - String errMsg = "Failure: " + getPageName() + ": Values are not correctly sorted in " + currentSortOrder + " order by: " + columnName; - System.err.println(errMsg); - logger.severe(errMsg); + Common.logError("Failure: " + getPageName() + " Unable to get sort order for column: " + columnKey); } } } - private String getCurrentSortingOrder(WebDriver driver, String columnKey) { + private String getCurrentSortingOrder(String columnKey) { + + try { + + WebElement columnHeader = Common.driver.findElement(By.xpath("//*[@*[.='" + columnKey + "']]")); - WebElement columnHeader = driver.findElement(By.cssSelector("div[data-item-key='" + columnKey + "']")); - columnHeader.click(); + String oldSortOrder = columnHeader.getAttribute("aria-sort"); - Common.sleep(); + columnHeader.click(); - columnHeader = driver.findElement(By.cssSelector("div[data-item-key='" + columnKey + "']")); + return waitToLoadChangedSortOrder(oldSortOrder, columnKey); - return columnHeader.getAttribute("aria-sort"); + } catch (Exception ex) { + Common.logError("Failure: " + getPageName() + " Exception in getting sort order for column: " + columnKey + " Error: " + ex.getMessage()); + } + + return null; } - private List getDataFromJSONUsingColumnKey(String columnKey, List jsonObjects) { + private String waitToLoadChangedSortOrder(String oldSortOrder, String columnKey) { + + int waitTimeInSecs = Config.WAIT_TIME_IN_SECONDS; + String newSortOrder; + + do { + Common.sleepWithTime(waitTimeInSecs); + + WebElement columnHeaderNew = Common.driver.findElement(By.xpath("//*[@*[.='" + columnKey + "']]")); + + newSortOrder = columnHeaderNew.getAttribute("aria-sort"); + + if (!Objects.equals(newSortOrder, oldSortOrder)) { + break; + } + + waitTimeInSecs++; + + } while (waitTimeInSecs < Config.WAIT_TIME_THRESHOLD_IN_SECONDS); + + return newSortOrder; + } + + private List getDataFromJSONUsingColumnKey(String columnKey) { List columnDataFromJSON = new ArrayList<>(); for (T jsonObject : jsonObjects) { columnDataFromJSON.add(getColumnDataFromJson(jsonObject, columnKey)); @@ -165,50 +385,80 @@ private List getDataFromJSONUsingColumnKey(String columnKey, List jso return columnDataFromJSON; } - private List getDataFromUIUsingColumnKey(WebDriver driver, String columnKey) { - List elements = driver.findElements(By.cssSelector("div[data-automation-key='" + columnKey + "']")); + protected List getDataFromUIUsingColumnKey(String columnKey) { + List columnData = new ArrayList<>(); - for (WebElement element : elements) { - columnData.add(element.getText()); + + try { + + List elements = waitToLoadListOfAllUIObjects(columnKey); + for (int i = 1; i < elements.size(); i++) { // first element fetched is of column header, so we fetch values from 2nd element. + columnData.add(elements.get(i).getText().trim()); + } + + } catch (Exception ex) { + Common.logError("Failure: " + getPageName() + ": Error in getting data from UI using column key: " + columnKey + "Error: " + ex.getMessage()); } + return columnData; } - void ascendingSortJson(List jsonObjects, String columnKey) { - jsonObjects.sort(Comparator.comparing(jsonObject -> (Comparable) getColumnDataFromJson(jsonObject, columnKey))); + private List waitToLoadListOfAllUIObjects(String columnKey) { + + int waitTimeInSecs = Config.WAIT_TIME_IN_SECONDS; + List elements; + + do { + elements = Common.driver.findElements(By.xpath("//*[@*[.='" + columnKey + "']]")); + + if (elements.size() - 1 >= jsonObjects.size()) { // first element fetched is of column header, so we do -1 from total elements + break; + } + + Common.sleepWithTime(waitTimeInSecs++); + + } while (waitTimeInSecs < Config.WAIT_TIME_THRESHOLD_IN_SECONDS); + + return elements; } - void descendingSortJson(List jsonObjects, String columnKey) { - jsonObjects.sort(Comparator.comparing((Function) jsonObject -> (Comparable) getColumnDataFromJson(jsonObject, columnKey)).reversed()); + void ascendingSortJson(String columnKey) { + try { + jsonObjects.sort(Comparator.comparing(jsonObject -> (Comparable) getColumnDataFromJson(jsonObject, columnKey))); + } catch (Exception ex) { + Common.logError("Failure: " + getPageName() + ": Exception in sorting JSON in ascending order using column key: " + columnKey + " Error: " + ex.getMessage()); + } } - private boolean testTableContent(WebDriver driver, Logger logger, List jsonObjects) { - System.out.println("Page: " + getPageName() + ": Number of Objects from Json: " + jsonObjects.size()); + void descendingSortJson(String columnKey) { + try { + jsonObjects.sort(Comparator.comparing((Function) jsonObject -> (Comparable) getColumnDataFromJson(jsonObject, columnKey)).reversed()); + } catch (Exception ex) { + Common.logError("Failure: " + getPageName() + ": Exception in sorting JSON in descending order using column key: " + columnKey + " Error: " + ex.getMessage()); + } + } - List columnDataIDFromUI = getDataFromUIUsingColumnKey(driver, getUniqueKey()); + private boolean testTableContent() { + Common.logDebug("Page: " + getPageName() + ": Number of Objects from Json: " + jsonObjects.size()); - if (jsonObjects.size() != columnDataIDFromUI.size()) { - Common.sleep(); - columnDataIDFromUI = getDataFromUIUsingColumnKey(driver, getUniqueKey()); - } + List columnDataIDFromUI = getDataFromUIUsingColumnKey(getUniqueKey()); - System.out.println("Page: " + getPageName() + ": Number of Objects from UI: " + columnDataIDFromUI.size()); + Common.logDebug("Page: " + getPageName() + ": Number of Objects from UI: " + columnDataIDFromUI.size()); if (jsonObjects.size() != columnDataIDFromUI.size()) { String errMsg = "Failure: " + getPageName() + ": Number of items on UI are not equal to the number of items in JSON" + "\nNumber of Objects from Json: " + jsonObjects.size() + "\nNumber of Objects from UI: " + columnDataIDFromUI.size(); - System.out.println(errMsg); - logger.severe(errMsg); + Common.logError(errMsg); return false; } boolean pass = true; for (int i = 0; i < getColumnKeys().length; i++) { - List columnDataFromUI = getDataFromUIUsingColumnKey(driver, getColumnKeys()[i]); - List columnDataFromJSON = getDataFromJSONUsingColumnKey(getColumnKeys()[i], jsonObjects); - if (!compareData(columnDataFromUI, columnDataFromJSON, columnDataIDFromUI, logger, getColumnNames()[i])) { + List columnDataFromUI = getDataFromUIUsingColumnKey(getColumnKeys()[i]); + List columnDataFromJSON = getDataFromJSONUsingColumnKey(getColumnKeys()[i]); + if (!compareData(columnDataFromUI, columnDataFromJSON, columnDataIDFromUI, getColumnNames()[i])) { pass = false; } } @@ -216,20 +466,18 @@ private boolean testTableContent(WebDriver driver, Logger logger, List jsonOb return pass; } - private List getAllObjectsFromJson(Logger logger) { + private List getAllObjectsFromJson() { String filePath = getJsonFilePath(); try { return parseJson(filePath); - } catch (Exception e) { - System.err.println("Exception: " + e.getMessage()); - logger.severe("Failure: Exception: " + e.getMessage()); + } catch (Exception ex) { + Common.logError("Failure: " + getPageName() + " Unable to parse JSON File: Exception: " + ex.getMessage()); } - logger.severe("Failure: Error in JSON Parsing: " + filePath); return null; } - private boolean compareData(List dataUI, List dataJSON, List dataIDUI, Logger logger, String columnName) { + private boolean compareData(List dataUI, List dataJSON, List dataIDUI, String columnName) { boolean pass = true; @@ -239,71 +487,91 @@ private boolean compareData(List dataUI, List dataJSON, List options = wait.until(ExpectedConditions.visibilityOfAllElementsLocatedBy(By.cssSelector(".ms-Dropdown-item"))); + WebElement dropdown = Common.waitForElement(By.id("pageSize")); + dropdown.click(); - int selectedValue = Config.dropdownValues[0]; + WebDriverWait wait = new WebDriverWait(Common.driver, Duration.ofSeconds(Config.WAIT_TIME_THRESHOLD_IN_SECONDS)); - // the smallest dropdown value greater than numOfItemsJSON - for (int value : Config.dropdownValues) { - if (numOfItemsJSON < value) { - selectedValue = value; - break; - } - } + WebElement dropdownList = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("pageSize-list"))); - System.out.println("Dropdown selected: " + selectedValue); + List options = dropdownList.findElements(By.tagName("button")); - for (WebElement option : options) { - if (option.getText().equals(String.valueOf(selectedValue))) { - option.click(); - break; + int selectedValue = Config.dropdownValues[0]; + + // the smallest dropdown value greater than numOfItemsJSON + for (int value : Config.dropdownValues) { + if (numOfItemsJSON < value) { + selectedValue = value; + break; + } + } + + for (WebElement option : options) { + if (option.getText().equals(String.valueOf(selectedValue))) { + option.click(); + break; + } } + + wait.until(ExpectedConditions.invisibilityOfElementLocated(By.id("pageSize-list"))); + + Common.logDebug("Page: " + getPageName() + ": Dropdown selected: " + selectedValue); + + Common.driver.navigate().refresh(); + Common.sleep(); + } catch (Exception ex) { + Common.logError("Failure: " + getPageName() + ": Error in clicking dropdown: " + ex.getMessage()); + } + } + + private String getSelectedDropdownValue() { + try { + WebElement dropdown = Common.waitForElement(By.id("pageSize")); + return dropdown.getText().trim(); + } catch (Exception ex) { + Common.logError("Failure: " + getPageName() + ": Error in getting dropdown value: " + ex.getMessage()); } - wait.until(ExpectedConditions.invisibilityOfElementLocated(By.cssSelector(".ms-Dropdown-item"))); - driver.navigate().refresh(); - Common.sleep(); + return ""; } - private String getSelectedDropdownValue(WebDriver driver) { - WebElement dropdown = waitForElement(driver, By.id("pageSize")); - return dropdown.getText().trim(); + protected WebElement getSaveButtonWebElementDetailsPage() { + return Common.waitForElement(By.xpath("//button//*[text()='" + getSaveButtonDetailsPage() + "']/../../..")); } - private void testForAllText(WebDriver driver, Logger logger) { + private void testForAllText() { + Common.logDebug("Tests started for: " + getPageName() + " page: Testing Text"); for (String text : getColumnNames()) { - Common.checkTextPresent(driver, text, getPageName(), logger); + Common.checkTextPresent(text, getPageName()); } } } diff --git a/esp/src/test-ui/tests/framework/pages/ECLWorkUnitsTest.java b/esp/src/test-ui/tests/framework/pages/ECLWorkUnitsTest.java index b5837fb4d6b..7f29811f6a1 100644 --- a/esp/src/test-ui/tests/framework/pages/ECLWorkUnitsTest.java +++ b/esp/src/test-ui/tests/framework/pages/ECLWorkUnitsTest.java @@ -2,21 +2,21 @@ import com.fasterxml.jackson.databind.ObjectMapper; import framework.config.Config; +import framework.config.URLConfig; import framework.model.ECLWorkunit; import framework.model.WUQueryResponse; import framework.model.WUQueryRoot; import framework.utility.Common; import framework.utility.TimeUtils; import org.openqa.selenium.By; -import org.openqa.selenium.NoSuchElementException; -import org.openqa.selenium.WebDriver; +import org.openqa.selenium.Keys; import org.openqa.selenium.WebElement; import org.testng.annotations.Test; import java.io.File; -import java.util.Arrays; -import java.util.List; -import java.util.logging.Logger; +import java.util.*; + +// This class is a subclass of BaseTableTest, and it includes test cases for ECL Workunits page and along with the tests of workunits details page. public class ECLWorkUnitsTest extends BaseTableTest { @@ -32,12 +32,12 @@ protected String getPageName() { @Override protected String getPageUrl() { - return Common.getUrl(Config.ECL_WORK_UNITS_URL); + return URLConfig.urlMap.get(URLConfig.NAV_ECL).getUrlMappings().get(URLConfig.TAB_ECL_WORKUNITS).getUrl(); } @Override protected String getJsonFilePath() { - return Common.isRunningOnLocal() ? Config.PATH_LOCAL_WORKUNITS_JSON : Config.PATH_GH_ACTION_WORKUNITS_JSON; + return Config.PATH_FOLDER_JSON + Config.WORKUNITS_JSON_FILE_NAME; } @Override @@ -50,6 +50,49 @@ protected String[] getColumnKeys() { return new String[]{"Wuid", "Owner", "Jobname", "Cluster", "State", "TotalClusterTime", "Compile Cost", "Execution Cost", "File Access Cost"}; } + private final List badStates = Arrays.asList("compiled", "failed"); + + @Override + protected String getSaveButtonDetailsPage() { + return "Save"; + } + + @Override + protected String[] getDetailNames() { + return new String[]{"WUID", "Action", "State", "Owner", "Job Name", "Description", "Potential Savings", "Compile Cost", "Execution Cost", "File Access Cost", "Protected", "Cluster", "Total Cluster Time", "Aborted by", "Aborted time", "Services"}; + } + + @Override + protected String[] getDetailKeys() { + WebElement element = Common.waitForElement(By.id("state")); + + if (badStates.contains(element.getAttribute(getAttributeValueForDetailsPage()))) { // compiled means it's published but not executed - no wu exists for this, check only these columns + return new String[]{"wuid", "action", "state", "owner", "jobname", "cluster"}; + } else { + return new String[]{"wuid", "action", "state", "owner", "jobname", "compileCost", "executeCost", "fileAccessCost", "protected", "cluster", "totalClusterTime"}; + } + } + + @Override + protected String[] getDetailKeysForPageLoad() { + return new String[]{"wuid", "state", "jobname", "cluster"}; + } + + @Override + protected String getCheckboxTypeForDetailsPage() { + return "checkbox"; + } + + @Override + protected String getAttributeTypeForDetailsPage() { + return "type"; + } + + @Override + protected String getAttributeValueForDetailsPage() { + return "value"; + } + @Override protected String[] getColumnKeysWithLinks() { return new String[]{"Wuid"}; @@ -65,63 +108,282 @@ protected String getUniqueKey() { return "Wuid"; } - private List getCostColumns() { - return Arrays.asList("Compile Cost", "Execution Cost", "File Access Cost"); + protected String[] getTabValuesForDetailsPage() { + return new String[]{"variables", "outputs", "inputs", "metrics", "workflows", + "queries", "resources", "helpers", "xml"}; + } + + @Override + protected Map> getColumnNamesForTabsDetailsPage() { // key in this map is the value attribute of the html element of a tab. + return Map.ofEntries( + Map.entry("variables", List.of("Type", "Name", "Value")), + Map.entry("outputs", List.of("Name", "File Name", "Value", "Views")), + Map.entry("inputs", List.of("Name", "File Cluster", "Usage")), + Map.entry("metrics", List.of("Refresh", "Hot spots", "Timeline", "Options")), // there is no column in this tab, so checking the presence of these buttons + Map.entry("workflows", List.of("Name", "Subtype", "Count", "Remaining")), + Map.entry("queries", List.of("ID", "Priority", "Name", "Target", "WUID", "Dll", "Published By", "Status")), + Map.entry("resources", List.of("Name", "Refresh", "Open", "Preview")), // Preview is not a column, it is a button, keeping it for additional check for Resources tab + Map.entry("helpers", List.of("Type", "Description", "File Size")), + Map.entry("xml", List.of(" costColumns = Arrays.asList("Compile Cost", "Execution Cost", "File Access Cost", "compileCost", "executeCost", "fileAccessCost"); + + @Override + protected Map getJsonMap() { + return jsonMap; } + @Override + protected void testDetailSpecificFunctionality(String wuName, int i) { + + if (!Config.TEST_WU_DETAIL_PAGE_PROTECTED_ALL && i == 0) { // TEST_WU_DETAIL_PAGE_PROTECTED_ALL = true means the test will run for all workunits and false means it will only run for the first workunit + testProtectedButtonFunctionality(wuName); + } else if (Config.TEST_WU_DETAIL_PAGE_PROTECTED_ALL) { + testProtectedButtonFunctionality(wuName); + } + + if (!Config.TEST_WU_DETAIL_PAGE_DESCRIPTION_ALL && i == 0) { // TEST_WU_DETAIL_PAGE_DESCRIPTION_ALL = true means the test will run for all workunits and false means it will only run for the first workunit + testDescriptionUpdateFunctionality(wuName); + } else if (Config.TEST_WU_DETAIL_PAGE_DESCRIPTION_ALL) { + testDescriptionUpdateFunctionality(wuName); + } + } + + private void testDescriptionUpdateFunctionality(String wuName) { + + Common.logDebug("Test started for: Description checkbox " + getPageName() + " Details Page."); + + try { + String newDescription = Config.TEST_DESCRIPTION_TEXT; + + String oldDescription = updateDescriptionAndSave(newDescription); + + testIfTheDescriptionUpdated(wuName, newDescription); + + Common.logDetail("Reverting Description Update To Old Value: " + getPageName() + " Details page, for WUID: " + wuName); + + updateDescriptionAndSave(oldDescription); + + } catch (Exception ex) { + Common.logError("Error: " + getPageName() + " Details page for WUID: " + wuName + ", Exception occurred while testing for description. Exception: " + ex.getMessage()); + } + } + + private void testIfTheDescriptionUpdated(String wuName, String newDescription) { + + Common.driver.navigate().refresh(); + + waitToLoadDetailsPage(); + + WebElement element = Common.waitForElement(By.id("description")); + + String updatedDescription = element.getAttribute(getAttributeValueForDetailsPage()).trim(); + + if (newDescription.equals(updatedDescription)) { + Common.logDetail("Success: " + getPageName() + " Details page. Description Updated Successfully on click of save button: Description After refresh showing on UI: " + updatedDescription + ", Updated description was: " + newDescription + " for WUID: " + wuName); + } else { + Common.logError("Failure: " + getPageName() + " Details page. Description Did Not Update on click of save button : Description After refresh showing on UI: " + updatedDescription + ", Updated description should be: " + newDescription + " for WUID: " + wuName); + } + } + + private String updateDescriptionAndSave(String newDescription) { + + waitToLoadDetailsPage(); + + WebElement element = Common.waitForElement(By.id("description")); + + String oldDescription = element.getAttribute(getAttributeValueForDetailsPage()); + + element.sendKeys(Keys.CONTROL + "a"); // Select all text + element.sendKeys(Keys.DELETE); // Delete all selected text + + element.sendKeys(newDescription); + + clickOnSaveButton(); + + element = Common.waitForElement(By.id("description")); + Common.logDetail("Old Description: " + oldDescription + ", Updated Description After Save: " + element.getAttribute(getAttributeValueForDetailsPage())); + + return oldDescription; + } + + private void testProtectedButtonFunctionality(String wuName) { + + Common.logDebug("Test started for: Protected checkbox " + getPageName() + " Details Page."); + + try { + + boolean newCheckboxValue = clickProtectedCheckboxAndSave(wuName); + + checkProtectedStatusOnECLWorkunitsPage(wuName, newCheckboxValue); + + WebElement element = Common.waitForElement(By.xpath("//div[text()='" + wuName + "']/..")); + element.click(); + + if (checkIfNewCheckBoxValuePresentInDetailsPage(newCheckboxValue)) { + Common.logDetail(getPageName() + " Details Page: Reverting the checkbox value for WUID: " + wuName); + clickProtectedCheckboxAndSave(wuName); + } + + } catch (Exception ex) { + Common.logError("Error: ECL WorkUnits: Exception in testing the protected functionality for WUID: " + wuName + ": Error: " + ex.getMessage()); + } + } + + private boolean checkIfNewCheckBoxValuePresentInDetailsPage(boolean newCheckboxValue) { + + waitToLoadDetailsPage(); + + WebElement protectedCheckbox = Common.waitForElement(By.id("protected")); + boolean currentCheckboxValue = protectedCheckbox.isSelected(); + + if (currentCheckboxValue != newCheckboxValue) { + Common.logError("Failure: " + getPageName() + " Details Page: Testing Protected checkbox value after coming back from ECL Workunits page: Updated value: " + newCheckboxValue + ": Showing: " + currentCheckboxValue + " on Workunits Details page"); + return false; + } else { + Common.logDetail("Success: " + getPageName() + " Details Page: Testing Protected checkbox value after coming back from ECL Workunits page: Updated value: " + newCheckboxValue + ": Showing: " + currentCheckboxValue + " on Workunits Details page"); + return true; + } + } + + private boolean clickProtectedCheckboxAndSave(String wuName) { + + waitToLoadDetailsPage(); + + WebElement protectedCheckbox = Common.waitForElement(By.id("protected")); + boolean oldCheckboxValue = protectedCheckbox.isSelected(); + + javaScriptElementClick(protectedCheckbox); + + waitToLoadUpdatedProtectedCheckbox(oldCheckboxValue); + + clickOnSaveButton(); + + boolean newCheckboxValue = protectedCheckbox.isSelected(); + + Common.logDetail("Protected checkbox old Value: " + oldCheckboxValue + ", current value after save: " + newCheckboxValue + " for WUID: " + wuName); + + return newCheckboxValue; + } + + private void waitToLoadUpdatedProtectedCheckbox(boolean oldCheckboxValue) { + + int waitTimeInSecs = Config.WAIT_TIME_IN_SECONDS; + boolean newCheckboxValue; + + do { + Common.sleepWithTime(waitTimeInSecs); + + WebElement protectedCheckbox = Common.waitForElement(By.id("protected")); + newCheckboxValue = protectedCheckbox.isSelected(); + + if (!Objects.equals(newCheckboxValue, oldCheckboxValue)) { + break; + } + + waitTimeInSecs++; + + } while (waitTimeInSecs < Config.WAIT_TIME_THRESHOLD_IN_SECONDS); + } + + private void checkProtectedStatusOnECLWorkunitsPage(String wuName, boolean newCheckboxValue) { + + Common.driver.navigate().to(getPageUrl()); + Common.driver.navigate().refresh(); + + List columnDataWUID = getDataFromUIUsingColumnKey(getUniqueKey()); + List columnDataProtected = getDataFromUIUsingColumnKey("Protected"); + + for (int i = 1; i < columnDataProtected.size(); i++) { // two web elements are getting fetched for Protected column header + + if (wuName.equals(columnDataWUID.get(i - 1))) { + + String lockStatus = !columnDataProtected.get(i).toString().isEmpty() ? "Locked" : "Unlocked"; + + if (newCheckboxValue && lockStatus.equals("Locked")) { + Common.logDetail("Success: " + getPageName() + " Details Page: Testing Protected checkbox for value: true : Showing Locked on ECL Workunits page"); + } else if (!newCheckboxValue && lockStatus.equals("Unlocked")) { + Common.logDetail("Success: " + getPageName() + " Details Page: Testing Protected checkbox for value: false : Showing Unlocked on ECL Workunits page"); + } else { + Common.logError("Failure: " + getPageName() + " Details Page: Testing Protected checkbox for value: " + newCheckboxValue + ": Showing: " + lockStatus + " on ECL Workunits page"); + } + } + } + } + + + Map jsonMap = new HashMap<>(); + @Override protected List parseJson(String filePath) throws Exception { ObjectMapper objectMapper = new ObjectMapper(); WUQueryRoot wuQueryRoot = objectMapper.readValue(new File(filePath), WUQueryRoot.class); WUQueryResponse wuQueryResponse = wuQueryRoot.getWUQueryResponse(); - return wuQueryResponse.getWorkunits().getECLWorkunit(); + List eclWorkunits = wuQueryResponse.getWorkunits().getECLWorkunit(); + + for (ECLWorkunit workunit : eclWorkunits) { + jsonMap.put(workunit.getWuid(), workunit); + } + + return eclWorkunits; } @Override protected Object getColumnDataFromJson(ECLWorkunit workunit, String columnKey) { return switch (columnKey) { - case "Wuid" -> workunit.getWuid(); - case "Owner" -> workunit.getOwner(); - case "Jobname" -> workunit.getJobname(); - case "Cluster" -> workunit.getCluster(); - case "State" -> workunit.getState(); - case "TotalClusterTime" -> workunit.getTotalClusterTime(); - case "Compile Cost" -> workunit.getCompileCost(); - case "Execution Cost" -> workunit.getExecuteCost(); - case "File Access Cost" -> workunit.getFileAccessCost(); + case "Wuid", "wuid" -> workunit.getWuid(); + case "Owner", "owner" -> workunit.getOwner(); + case "Jobname", "jobname" -> workunit.getJobname(); + case "Cluster", "cluster" -> workunit.getCluster(); + case "State", "state" -> workunit.getState(); + case "TotalClusterTime", "totalClusterTime" -> workunit.getTotalClusterTime(); + case "Compile Cost", "compileCost" -> workunit.getCompileCost(); + case "Execution Cost", "executeCost" -> workunit.getExecuteCost(); + case "File Access Cost", "fileAccessCost" -> workunit.getFileAccessCost(); + case "action" -> workunit.getActionEx(); + case "protected" -> workunit.isProtected(); default -> null; }; } @Override - protected Object parseDataUIValue(Object dataUIValue, String columnName, Object dataIDUIValue, Logger logger) { - if (getCostColumns().contains(columnName)) { - dataUIValue = Double.parseDouble(((String) dataUIValue).split(" ")[0]); - } else if (columnName.equals("Total Cluster Time")) { - long timeInMilliSecs = TimeUtils.convertToMilliseconds((String) dataUIValue); - if (timeInMilliSecs == Config.MALFORMED_TIME_STRING) { - String errMsg = "Failure: " + getPageName() + ": Incorrect time format for " + columnName + " : " + dataUIValue + " in UI for " + getUniqueKeyName() + " : " + dataIDUIValue; - System.out.println(errMsg); - logger.severe(errMsg); - return dataUIValue; - } + protected Object parseDataUIValue(Object dataUIValue, Object dataJSONValue, String columnName, Object dataIDUIValue) { + try { + if (costColumns.contains(columnName)) { + dataUIValue = Double.parseDouble(((String) dataUIValue).split(" ")[0]); + } else if (columnName.equals("Total Cluster Time") || columnName.equals("totalClusterTime")) { - return timeInMilliSecs; - } else if (dataUIValue instanceof String) { - dataUIValue = ((String) dataUIValue).trim(); + if ((long) dataJSONValue == 0 && "".equals(dataUIValue)) { + return 0L; // test ok, if Total Cluster Time is 0 in json and blank in UI - Jira raised - HPCC-32147 + } + + long timeInMilliSecs = TimeUtils.convertToMilliseconds((String) dataUIValue); + if (timeInMilliSecs == Config.MALFORMED_TIME_STRING) { + String errMsg = "Failure: " + getPageName() + ": Incorrect time format for " + columnName + " : " + dataUIValue + " in UI for " + getUniqueKeyName() + " : " + dataIDUIValue; + Common.logError(errMsg); + return dataUIValue; + } + + return timeInMilliSecs; + } else if (dataUIValue instanceof String) { + dataUIValue = ((String) dataUIValue).trim(); + } + } catch (Exception ex) { + Common.logError("Failure: " + getPageName() + " Error in parsing UI value: " + dataUIValue + " for column: " + columnName + " ID: " + dataIDUIValue + " Error: " + ex.getMessage()); } return dataUIValue; } @Override - protected Object parseDataJSONValue(Object dataJSONValue, String columnName, Object dataIDUIValue, Logger logger) { - if (columnName.equals("Total Cluster Time")) { + protected Object parseDataJSONValue(Object dataJSONValue, String columnName, Object dataIDUIValue) { + if (columnName.equals("Total Cluster Time") || columnName.equals("totalClusterTime")) { long timeInMilliSecs = (long) dataJSONValue; if (timeInMilliSecs == Config.MALFORMED_TIME_STRING) { String errMsg = "Failure: " + getPageName() + ": Incorrect time format for " + columnName + " in JSON for " + getUniqueKeyName() + " : " + dataIDUIValue; - System.out.println(errMsg); - logger.severe(errMsg); + Common.logError(errMsg); return dataJSONValue; } @@ -132,23 +394,23 @@ protected Object parseDataJSONValue(Object dataJSONValue, String columnName, Obj } @Override - protected void sortJsonUsingSortOrder(String currentSortOrder, List workunitsJson, String columnKey) { + protected void sortJsonUsingSortOrder(String currentSortOrder, String columnKey) { switch (currentSortOrder) { - case "ascending" -> ascendingSortJson(workunitsJson, columnKey); - case "descending" -> descendingSortJson(workunitsJson, columnKey); - case "none" -> descendingSortJson(workunitsJson, getUniqueKey()); + case "ascending" -> ascendingSortJson(columnKey); + case "descending" -> descendingSortJson(columnKey); + case "none" -> descendingSortJson(getUniqueKey()); } } @Override - protected String getCurrentPage(WebDriver driver) { + protected String getCurrentPage() { try { - WebElement element = waitForElement(driver, By.id("wuid")); + WebElement element = Common.waitForElement(By.id("wuid")); if (element != null) { return element.getAttribute("title"); } - } catch (NoSuchElementException e) { - return "Invalid Page"; + } catch (Exception ex) { + Common.logError("Error: " + getPageName() + ex.getMessage()); } return "Invalid Page"; } diff --git a/esp/src/test-ui/tests/framework/setup/LoggerHolder.java b/esp/src/test-ui/tests/framework/setup/LoggerHolder.java deleted file mode 100644 index b80c46f471f..00000000000 --- a/esp/src/test-ui/tests/framework/setup/LoggerHolder.java +++ /dev/null @@ -1,16 +0,0 @@ -package framework.setup; - -import java.util.logging.Logger; - -public class LoggerHolder { - private static Logger logger; - - public static Logger getLogger() { - return logger; - } - - public static void setLogger(Logger logger) { - LoggerHolder.logger = logger; - } -} - diff --git a/esp/src/test-ui/tests/framework/setup/TestInjector.java b/esp/src/test-ui/tests/framework/setup/TestInjector.java deleted file mode 100644 index 48742c85d51..00000000000 --- a/esp/src/test-ui/tests/framework/setup/TestInjector.java +++ /dev/null @@ -1,31 +0,0 @@ -package framework.setup; - -import org.openqa.selenium.WebDriver; -import org.testng.IInvokedMethod; -import org.testng.IInvokedMethodListener; -import org.testng.ITestResult; - -import java.util.logging.Logger; - -public class TestInjector implements IInvokedMethodListener { - private final Logger logger; - private final WebDriver driver; - - public TestInjector(Logger logger, WebDriver driver) { - this.logger = logger; - this.driver = driver; - } - - @Override - public void beforeInvocation(IInvokedMethod method, ITestResult testResult) { - if (method.isTestMethod()) { - LoggerHolder.setLogger(logger); - WebDriverHolder.setDriver(driver); - } - } - - @Override - public void afterInvocation(IInvokedMethod method, ITestResult testResult) { - // Optionally, clean up if necessary - } -} diff --git a/esp/src/test-ui/tests/framework/setup/WebDriverHolder.java b/esp/src/test-ui/tests/framework/setup/WebDriverHolder.java deleted file mode 100644 index 0daf2f245aa..00000000000 --- a/esp/src/test-ui/tests/framework/setup/WebDriverHolder.java +++ /dev/null @@ -1,15 +0,0 @@ -package framework.setup; - -import org.openqa.selenium.WebDriver; - -public class WebDriverHolder { - private static WebDriver driver; - - public static WebDriver getDriver() { - return driver; - } - - public static void setDriver(WebDriver driver) { - WebDriverHolder.driver = driver; - } -} diff --git a/esp/src/test-ui/tests/framework/utility/Common.java b/esp/src/test-ui/tests/framework/utility/Common.java index 2fb12970a09..ca084f72242 100644 --- a/esp/src/test-ui/tests/framework/utility/Common.java +++ b/esp/src/test-ui/tests/framework/utility/Common.java @@ -1,36 +1,63 @@ package framework.utility; import framework.config.Config; -import org.openqa.selenium.WebDriver; +import framework.config.URLConfig; +import org.openqa.selenium.*; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; +import java.io.IOException; import java.time.Duration; +import java.util.logging.FileHandler; +import java.util.logging.Level; import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; public class Common { - public static void checkTextPresent(WebDriver driver, String text, String page, Logger logger) { - if (driver.getPageSource().contains(text)) { - String msg = "Success: " + page + ": Text present: " + text; - System.out.println(msg); - } else { + public static WebDriver driver; + public static Logger errorLogger = setupLogger("error"); + public static Logger specificLogger; + + public static void checkTextPresent(String text, String page) { + try { + WebElement element = Common.waitForElement(By.xpath("//*[text()='" + text + "']")); + if (element != null) { + String msg = "Success: " + page + ": Text present: " + text; + logDetail(msg); + } + } catch (TimeoutException ex) { String errorMsg = "Failure: " + page + ": Text not present: " + text; - System.err.println(errorMsg); - logger.severe(errorMsg); + logError(errorMsg); } } - public static void openWebPage(WebDriver driver, String url) { - - driver.get(url); - driver.manage().window().maximize(); - sleep(); + public static void openWebPage(String url) { + try { + driver.get(url); + driver.manage().window().maximize(); + sleep(); + } catch (Exception ex) { + Common.logError("Error in opening web page: " + url); + } } public static void sleep() { try { Thread.sleep(Duration.ofSeconds(Config.WAIT_TIME_IN_SECONDS)); } catch (InterruptedException e) { - System.err.println(e.getMessage()); + Common.logError("Error in sleep: " + e.getMessage()); + } + } + + public static void sleepWithTime(int seconds) { + try { + Thread.sleep(Duration.ofSeconds(seconds)); + } catch (InterruptedException e) { + Common.logError("Error in sleep: " + e.getMessage()); } } @@ -38,8 +65,131 @@ public static boolean isRunningOnLocal() { return System.getProperty("os.name").startsWith(Config.LOCAL_OS) && System.getenv("USERPROFILE").startsWith(Config.LOCAL_USER_PROFILE); } - public static String getUrl(String url) { + public static String getIP() { + + return isRunningOnLocal() ? URLConfig.LOCAL_IP : URLConfig.GITHUB_ACTION_IP; + } + + public static WebElement waitForElement(By locator) { + return new WebDriverWait(driver, Duration.ofSeconds(Config.WAIT_TIME_THRESHOLD_IN_SECONDS)).until(ExpectedConditions.presenceOfElementLocated(locator)); + } + + public static void waitForElementToBeClickable(WebElement element) { + new WebDriverWait(driver, Duration.ofSeconds(Config.WAIT_TIME_THRESHOLD_IN_SECONDS)).until(ExpectedConditions.elementToBeClickable(element)); + } + + // aria-disabled is a standard attribute and is widely used in HTML to indicate whether an element is disabled. It is system-defined, meaning it is part of the standard HTML specifications and not a custom class or attribute that might change frequently. + // By using aria-disabled, you ensure that the check for the disabled state is consistent and less likely to break due to UI changes. Custom class names or attributes defined by developers can change frequently during updates or redesigns, but standard attributes like aria-disabled are much more stable. + + public static void waitForElementToBeDisabled(WebElement element) { + new WebDriverWait(driver, Duration.ofSeconds(Config.WAIT_TIME_THRESHOLD_IN_SECONDS)) + .until(ExpectedConditions.attributeContains(element, "aria-disabled", "true")); + } + + public static void logError(String message) { + System.err.println(message); + errorLogger.severe(message); + } + + public static void logDebug(String message) { + System.out.println(message); + + if (specificLogger != null && specificLogger.getLevel() == Level.INFO) { + specificLogger.info(message); + } + + if (specificLogger != null && specificLogger.getLevel() == Level.FINE) { + specificLogger.fine(message); + } + } + + public static void logDetail(String message) { + System.out.println(message); + + if (specificLogger != null && specificLogger.getLevel() == Level.FINE) { + specificLogger.fine(message); + } + } + + public static void initializeLoggerAndDriver() { + specificLogger = setupLogger(Config.LOG_LEVEL); + driver = setupWebDriver(); + } + + private static WebDriver setupWebDriver() { + + try { + ChromeOptions chromeOptions = new ChromeOptions(); + chromeOptions.addArguments("--headless"); // sets the ChromeDriver to run in headless mode, meaning it runs without opening a visible browser window. + chromeOptions.addArguments("--no-sandbox"); // disables the sandbox security feature in Chrome. + chromeOptions.addArguments("--log-level=3"); // sets the log level for the ChromeDriver. Level 3 corresponds to errors only. + + System.setProperty("webdriver.chrome.silentOutput", "true"); // suppresses the logs generated by the ChromeDriver + + if (Common.isRunningOnLocal()) { + System.setProperty("webdriver.chrome.driver", Config.PATH_LOCAL_CHROME_DRIVER); // sets the system property to the path of the ChromeDriver executable. + } else { + System.setProperty("webdriver.chrome.driver", Config.PATH_GH_ACTION_CHROME_DRIVER); + } + + RemoteWebDriver driver = new ChromeDriver(chromeOptions); + + Capabilities caps = driver.getCapabilities(); + String browserName = caps.getBrowserName(); + String browserVersion = caps.getBrowserVersion(); + System.out.println(browserName + " " + browserVersion); + + return driver; + } catch (Exception ex) { + errorLogger.severe("Failure: Error in setting up web driver: " + ex.getMessage()); + } + + return null; + } + + private static Logger setupLogger(String logLevel) { + + if (logLevel.isEmpty()) { + return null; + } + + Logger logger = Logger.getLogger(logLevel); + logger.setUseParentHandlers(false); // Disable console logging + + try { + if (logLevel.equalsIgnoreCase("error")) { + FileHandler errorFileHandler = new FileHandler(Config.LOG_FILE_ERROR); + errorFileHandler.setFormatter(new SimpleFormatter()); + logger.addHandler(errorFileHandler); + logger.setLevel(Level.SEVERE); + } else if (logLevel.equalsIgnoreCase("debug")) { + FileHandler debugFileHandler = new FileHandler(Config.LOG_FILE_DEBUG); + debugFileHandler.setFormatter(new SimpleFormatter()); + logger.addHandler(debugFileHandler); + logger.setLevel(Level.INFO); + } else if (logLevel.equalsIgnoreCase("detail")) { + FileHandler detailFileHandler = new FileHandler(Config.LOG_FILE_DETAIL); + detailFileHandler.setFormatter(new SimpleFormatter()); + logger.addHandler(detailFileHandler); + logger.setLevel(Level.FINE); + } + } catch (IOException e) { + System.err.println("Failure: Failed to setup logger: " + e.getMessage()); + } + + Logger.getLogger("org.openqa.selenium").setLevel(Level.OFF); // Turn off all logging from the Selenium WebDriver. + return logger; + } + + public static String[] getAttributeList(WebElement webElement, String pageName) { + try { + JavascriptExecutor executor = (JavascriptExecutor) driver; + Object attributes = executor.executeScript("var items = {}; for (index = 0; index < arguments[0].attributes.length; ++index) { items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value }; return items;", webElement); + return attributes.toString().replaceAll("[{}]", "").split(", "); + } catch (Exception ex) { + Common.logError("Failure: " + pageName + ": Error in getting arguments list for web element: " + webElement.getText() + "Error: " + ex.getMessage()); + } - return isRunningOnLocal() ? Config.LOCAL_IP + url : Config.GITHUB_ACTION_IP + url; + return new String[0]; } }