diff --git a/.github/workflows/data-dictionary-tests.yml b/.github/workflows/data-dictionary-tests.yml index 71d393d06..6ceca5654 100644 --- a/.github/workflows/data-dictionary-tests.yml +++ b/.github/workflows/data-dictionary-tests.yml @@ -83,7 +83,7 @@ jobs: # Extract variables from the api_test_counts.txt file while IFS= read -r line; do echo "::set-output name=${line%=*}::${line#*=}" - done < data_dictionary_test_counts_${{ matrix.environment }}.txt + done < data_dictionary_tests_counts_${{ matrix.environment }}.txt - name: Archive test results id: artifact-upload-step diff --git a/.github/workflows/power-bi-tests.yml b/.github/workflows/power-bi-tests.yml index 3482c1b85..795cde5b6 100644 --- a/.github/workflows/power-bi-tests.yml +++ b/.github/workflows/power-bi-tests.yml @@ -73,7 +73,7 @@ jobs: echo "matrix environment: ${{ matrix.environment }}" echo "NOW=$(date +'%m-%d %H:%M')" >> $GITHUB_ENV echo ${{env.NOW}} - pytest -v --rootdir= Features/Powerbi_integration_exports/testCases -n 0 --dist=loadfile --reruns 1 --html=power_bi_reports_${{ matrix.environment }}.html + pytest -v --rootdir= Features/Powerbi_integration_exports/testCases -n 1 --dist=loadfile --reruns 1 --html=power_bi_reports_${{ matrix.environment }}.html - name: Parse test counts diff --git a/.gitignore b/.gitignore index f47195c5a..d3b95614a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,8 @@ MANIFEST *.manifest *.spec +*/slack_charts/* + #Ignoring compliled files of RequestAPI .idea/ pytest_html_report.html @@ -238,6 +240,17 @@ Features/Lookuptable/settings.cfg Features/Lookuptable/report.html Features/Lookuptable/testCases/report.html +#Ignoring compliled files of DataDictionary +Features/DataDictionary/testCases/report.html +Features/DataDictionary/testCases/slack_charts +Features/DataDictionary/test_cases/report.html + +#Ignoring compliled files of Powerbi_integration_exports +Features/Powerbi_integration_exports/settings.cfg +Features/Powerbi_integration_exports/report.html +Features/Powerbi_integration_exports/test_cases/report.html +Features/Powerbi_integration_exports/testCases/slack_charts + #Ignoring compliled files of QA_Requests QA_Requests/BHAStressTest/settings.cfg QA_Requests/BHAStressTest/user_inputs/*.csv @@ -277,6 +290,8 @@ LocustScripts/update-scripts/project-config/co-carecoordination-perf/mobile_work /POCs/PercyWebApps/settings.cfg /POCs/VisualComparison/settings.cfg + + USH_Apps/WeeklyBHACleanup/testCases/report.html USH_Apps/WeeklyBHACleanup/report.html USH_Apps/WeeklyBHACleanup/report_* diff --git a/Features/DataDictionary/README.md b/Features/DataDictionary/README.md new file mode 100644 index 000000000..88116b467 --- /dev/null +++ b/Features/DataDictionary/README.md @@ -0,0 +1,57 @@ +## Commcare Data Dictionary Test Script + +These tests ensure that the Data Dictionary (https://dimagi.atlassian.net/wiki/spaces/commcarepublic/pages/2143944977/Data+Dictionary)features work as expected and that there are no regressions +The automated tests comprises of the Data dictionary functionality.](https://docs.google.com/spreadsheets/d/1Ixw1PC9PVpqYOlP7aq9a86pC0wZaJVgGNVL_4h9KLbM/edit#gid=195915568) +## Executing Scripts + +### On Local Machine + +#### Setting up test environment + +```sh + +# create and activate a virtualenv using your preferred method. Example: +python -m venv venv +source venv/bin/activate + + +# install requirements +pip install -r requires.txt + +``` + +[More on setting up virtual environments](https://confluence.dimagi.com/display/GTD/QA+and+Python+Virtual+Environments) + + +#### Running Tests + + + - Copy `settings-sample.cfg` to `settings.cfg` and populate `settings.cfg` for +the environment you want to test. +- Run tests using pytest command like: + +```sh + +# To execute all the test cases +pytest -v --rootdir= Features/DataDictionary/testCases + +``` +- You could also pass the following arguments + - ` -n 3 --dist=loadfile` - This will run the tests parallelly in 3 instances. The number of reruns is configurable. + - ` --reruns 1` - This will re-run the tests once in case of failures.The number of reruns is configurable too. + +### Trigger Manually on Gitaction + +clone this repository + +To manually trigger the script, + - Go to [DataDictionary](https://github.com/dimagi/dimagi-qa/actions/lookuptable.yml) + - Run workflow + - Use workflow from ```master``` + - Run! + +If you are a part of the QA team, you'll receive emails for the result of the run after it's complete. + +clone this repository + +Besides, you should be able to find the zipped results in the **Artifacts** section, of the corresponding run (after it's complete). diff --git a/Features/DataDictionary/__init__.py b/Features/DataDictionary/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Features/DataDictionary/requires.txt b/Features/DataDictionary/requires.txt new file mode 100644 index 000000000..0fc230cfa --- /dev/null +++ b/Features/DataDictionary/requires.txt @@ -0,0 +1,22 @@ +## Stores information about all the libraries, modules, and packages that are used in this project. + + +flake8>=3.8.4 +pandas>=1.2.2 +pytest +py>=1.10.0 +pytest-html>=3.1.1 +pytest-json-report +selenium == 4.11.0 +openpyxl +matplotlib >= 3.3.4 +pytest-rerunfailures +pytest-xdist +pytest-xdist[psutil] +pytest-order +requests +imap-tools +beautifulsoup4 +html5lib +pytest-metadata +pyotp >=2.6.0 \ No newline at end of file diff --git a/Features/DataDictionary/settings-sample.cfg b/Features/DataDictionary/settings-sample.cfg new file mode 100644 index 000000000..b6bb402bb --- /dev/null +++ b/Features/DataDictionary/settings-sample.cfg @@ -0,0 +1,12 @@ +[default] +# This is the environment url of commcare +url = https://www.commcarehq.org/ +# Login username of the webuser +login_username = +# Login password of the webuser +login_password = +# This is a preconfigured authentication key used for 2FA tests on staging - If 2FA enabled on staging. +staging_auth_key = +# This is a preconfigured authentication key used for 2FA tests on prod +prod_auth_key = + diff --git a/Features/DataDictionary/testCases/__init__.py b/Features/DataDictionary/testCases/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Features/DataDictionary/testCases/conftest.py b/Features/DataDictionary/testCases/conftest.py new file mode 100644 index 000000000..01615e904 --- /dev/null +++ b/Features/DataDictionary/testCases/conftest.py @@ -0,0 +1,295 @@ +import os + +from configparser import ConfigParser +from pathlib import Path +from common_utilities.fixtures import * + +""""This file provides fixture functions for driver initialization""" + +global driver + + +@pytest.fixture(scope="session") +def environment_settings_lookup(): + """Load settings from os.environ + + Names of environment variables: + DIMAGIQA_URL + DIMAGIQA_LOGIN_USERNAME + DIMAGIQA_LOGIN_PASSWORD + DIMAGIQA_MAIL_USERNAME + DIMAGIQA_MAIL_PASSWORD + + See https://docs.github.com/en/actions/reference/encrypted-secrets + for instructions on how to set them. + """ + settings = {} + for name in ["url", "login_username", "login_password", "staging_auth_key", "prod_auth_key"]: + + var = f"DIMAGIQA_{name.upper()}" + if var in os.environ: + settings[name] = os.environ[var] + if "url" not in settings: + env = os.environ.get("DIMAGIQA_ENV") or "staging" + subdomain = "www" if env == "production" else env + # updates the url with the project domain while testing in CI + project = "a/qa-automation-prod" if env == "production" else "a/qa-automation" + settings["url"] = f"https://{subdomain}.commcarehq.org/{project}" + return settings + + +@pytest.fixture(scope="session", autouse=True) +def settings(environment_settings_lookup): + if os.environ.get("CI") == "true": + settings = environment_settings_lookup + settings["CI"] = "true" + if any(x not in settings for x in ["url", "login_username", "login_password", + "staging_auth_key", "prod_auth_key"]): + lines = environment_settings_lookup.__doc__.splitlines() + vars_ = "\n ".join(line.strip() for line in lines if "DIMAGIQA_" in line) + raise RuntimeError( + f"Environment variables not set:\n {vars_}\n\n" + "See https://docs.github.com/en/actions/reference/encrypted-secrets " + "for instructions on how to set them." + ) + return settings + path = Path(__file__).parent.parent / "settings.cfg" + if not path.exists(): + raise RuntimeError( + f"Not found: {path}\n\n" + "Copy settings-sample.cfg to settings.cfg and populate " + "it with values for the environment you want to test." + ) + settings = ConfigParser() + settings.read(path) + # updates the url with the project domain while testing in local + if settings["default"]["url"] == "https://www.commcarehq.org/": + settings["default"]["url"] = f"{settings['default']['url']}a/qa-automation-prod" + else: + settings["default"]["url"] = f"{settings['default']['url']}a/qa-automation" + return settings["default"] + +def pytest_terminal_summary(terminalreporter, exitstatus, config): + # Collect test counts + passed = terminalreporter.stats.get('passed', []) + failed = terminalreporter.stats.get('failed', []) + error = terminalreporter.stats.get('error', []) + skipped = terminalreporter.stats.get('skipped', []) + xfail = terminalreporter.stats.get('xfail', []) + + env = os.environ.get("DIMAGIQA_ENV", "default_env") + + # Define the filename based on the environment + filename = f'data_dictionary_tests_counts_{env}.txt' + + # Write the counts to a file + with open(filename, 'w') as f: + f.write(f'PASSED={len(passed)}\n') + f.write(f'FAILED={len(failed)}\n') + f.write(f'ERROR={len(error)}\n') + f.write(f'SKIPPED={len(skipped)}\n') + f.write(f'XFAIL={len(xfail)}\n') + +# conftest.py +import pytest +import matplotlib.pyplot as plt +import base64 +from io import BytesIO + +_test_stats = {} + +def pytest_sessionfinish(session, exitstatus): + """Collect stats at the end of the test session.""" + tr = session.config.pluginmanager.get_plugin("terminalreporter") + global _test_stats + _test_stats = { + "passed": len(tr.stats.get("passed", [])), + "failed": len(tr.stats.get("failed", [])), + "skipped": len(tr.stats.get("skipped", [])), + "error": len(tr.stats.get("error", [])), + "xfail": len(tr.stats.get("xfail", [])), + "reruns": len(tr.stats.get("rerun", [])), + } + save_summary_charts(_test_stats) + +import base64 + +def save_summary_charts(stats): + from pathlib import Path + out_dir = Path("slack_charts") + out_dir.mkdir(exist_ok=True) + + passed = stats.get("passed", 0) + failed = stats.get("failed", 0) + skipped = stats.get("skipped", 0) + reruns = stats.get("reruns", 0) + + # --- Pie chart with legend --- + pie_labels = ["Passed", "Failed", "Skipped"] + pie_sizes = [passed, failed, skipped] + pie_colors = ["#66bb6a", "#ef5350", "#fad000"] + + fig, ax = plt.subplots() + wedges, texts = ax.pie( + pie_sizes, + labels=None, + colors=pie_colors, + startangle=90, + wedgeprops=dict(width=0.4) + ) + ax.axis("equal") + ax.set_title("Test Summary") + + # Add legend with counts + ax.legend( + [f"Passed: {passed}", f"Failed: {failed}", f"Skipped: {skipped}"], + loc="lower center", + ncol=3, + bbox_to_anchor=(0.5, -0.15) + ) + + fig.savefig(out_dir / "summary_pie.png", bbox_inches="tight") + plt.close(fig) + + # --- Bar chart with labels + legend --- + bar_path = None + if failed > 0 or reruns > 0: # ✅ only generate if needed + fig, ax = plt.subplots() + bars = ax.bar( + ["Failed", "Reruns"], + [failed, reruns], + color=["#ef5350", "#ffa726"] + ) + ax.set_ylabel("Number of Tests") + ax.set_title("Failures and Reruns") + + # Add counts above bars + for bar in bars: + height = bar.get_height() + ax.text( + bar.get_x() + bar.get_width() / 2, + height + 0.05, + str(int(height)), + ha="center", + va="bottom", + fontsize=10, + fontweight="bold" + ) + + # Legend with counts + ax.legend( + [f"Failed: {failed}", f"Reruns: {reruns}"], + loc="lower center", + ncol=2, + bbox_to_anchor=(0.5, -0.15) + ) + + bar_path = out_dir / "summary_bar.png" + fig.savefig(bar_path, bbox_inches="tight") + plt.close(fig) + + # --- Combine --- + combine_charts( + pie_path=out_dir / "summary_pie.png", + bar_path=bar_path, + combined_path=out_dir / "summary_combined.png" + ) + + +import matplotlib.pyplot as plt +from PIL import Image + +def combine_charts(pie_path="slack_charts/summary_pie.png", + bar_path=None, + combined_path="slack_charts/summary_combined.png"): + """Combine pie and bar charts side by side if bar exists, else only pie.""" + from PIL import Image + + pie = Image.open(pie_path) + + if bar_path and Path(bar_path).exists(): + bar = Image.open(bar_path) + bar = bar.resize((bar.width * pie.height // bar.height, pie.height)) + combined = Image.new("RGB", (pie.width + bar.width, pie.height), (255, 255, 255)) + combined.paste(pie, (0, 0)) + combined.paste(bar, (pie.width, 0)) + else: + # Only pie chart + combined = pie.copy() + + combined.save(combined_path) + print(f"✅ Combined chart saved to {combined_path}") + + + +def _matplotlib_img(fig) -> str: + """Convert a matplotlib figure to base64 string.""" + buf = BytesIO() + plt.tight_layout() + fig.savefig(buf, format="png") + plt.close(fig) + buf.seek(0) + return base64.b64encode(buf.read()).decode("utf-8") + +def pytest_html_results_summary(prefix, summary, postfix, session): + """Inject donut pie + bar chart with reruns support (parallel-safe).""" + tr = session.config.pluginmanager.get_plugin("terminalreporter") + stats = tr.stats if tr and hasattr(tr, "stats") else {} + + passed = len(stats.get("passed", [])) + failed = len(stats.get("failed", [])) + skipped = len(stats.get("skipped", [])) + + # Reruns are recorded separately by pytest-rerunfailures + reruns = len(stats.get("rerun", [])) + + # --- Donut Pie Chart (Passed, Failed, Skipped) --- + pie_labels = ["Passed", "Failed", "Skipped"] + pie_sizes = [passed, failed, skipped] + pie_colors = ["#66bb6a", "#ef5350", "#fad000"] + + fig, ax = plt.subplots() + wedges, texts = ax.pie( + pie_sizes, + labels=None, + colors=pie_colors, + startangle=90, + wedgeprops=dict(width=0.4) + ) + ax.axis("equal") + + # Legend below the donut + plt.legend( + wedges, + [f"{l}: {v}" for l, v in zip(pie_labels, pie_sizes)], + title="Results", + loc="upper center", + bbox_to_anchor=(0.5, -0.08), + ncol=len(pie_labels) + ) + pie_img = _matplotlib_img(fig) + + # --- Bar Chart (Failures + Reruns) --- + bar_img = None + if failed > 0 or reruns > 0: + fig, ax = plt.subplots() + bars = ax.bar(["Failed", "Reruns"], [failed, reruns], color=["#ef5350", "#ff9933"]) + ax.set_title("Failures and Reruns") + ax.set_ylabel("Number of Tests") + plt.legend( + bars, + [f"Failed: {failed}", f"Reruns: {reruns}"], + loc="upper center", + bbox_to_anchor=(0.5, -0.12), + ncol=2 + ) + bar_img = _matplotlib_img(fig) + + # --- Embed in HTML report --- + html = "
" + html += f"

Test Summary

" + if bar_img: + html += f"

Failures and Reruns

" + html += "
" + + summary.append(html) diff --git a/Features/DataDictionary/testCases/test_01_data_dictionary_tests.py b/Features/DataDictionary/testCases/test_01_data_dictionary_tests.py new file mode 100644 index 000000000..d134751a4 --- /dev/null +++ b/Features/DataDictionary/testCases/test_01_data_dictionary_tests.py @@ -0,0 +1,313 @@ +import time + +import pytest + +from Features.DataDictionary.userInputs.user_inputs import UserData +from HQSmokeTests.testPages.data.import_cases_page import ImportCasesPage +from HQSmokeTests.testPages.home.home_page import HomePage +from HQSmokeTests.testPages.applications.application_page import ApplicationPage +from Features.DataDictionary.testPages.data.data_dictionary_page import DataDictionaryPage +from HQSmokeTests.testPages.messaging.messaging_page import MessagingPage +from HQSmokeTests.testPages.users.roles_permissions_page import RolesPermissionPage +from common_utilities.hq_login.login_page import LoginPage +from HQSmokeTests.testPages.users.web_user_page import WebUsersPage +from HQSmokeTests.testPages.users.org_structure_page import latest_download_file + + +""""Contains test cases related to the Data module""" + +values = dict() + +@pytest.mark.lookup +def test_case_01_verify_data_dictionary_ui(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("Y") + data.verify_dropdown_values() + +def test_case_02_validate_editing_case_property_values(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.edit_case_property_description() + +def test_case_03_validate_case_property_addition(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.add_new_case_property() + data.case_property_deletion() + +def test_case_04_validate_case_group_addition(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.adding_a_new_group() + +def test_case_05_verify_deprecate_property(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("Y") + data.verify_deprecate_restore_case_property("Y") + +def test_case_06_verify_add_group_description(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.updating_group_description() + +def test_case_07_verify_downloading_dd_file(driver, settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.verify_file_getting_downloaded() + +def test_case_08_verify_uploading_dd_file(driver, settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + download_path =latest_download_file() + data.verify_uploading_dd(download_path) + +def test_case_09_validate_deprecate_case_type(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.case_type_deprecate() + data.case_type_restore() + +def test_case_10_validate_deprecate_case_types_reports(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.case_type_deprecate() + home.reports_menu() + data.verify_reports() + home.data_menu() + data.case_type_restore() + +def test_case_11_validate_deprecate_restore_data_exports(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.case_type_deprecate() + home.data_menu() + data.verify_exports() + home.data_menu() + data.case_type_restore() + home.data_menu() + data.verify_exports() + +def test_case_12_validate_deprecate_restore_case_exports(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.create_case_export() + home.data_menu() + data.verify_data_page("N") + data.case_type_deprecate() + data.validate_exports() + data.case_type_restore() + home.data_menu() + data.validate_exports() + +def test_case_13_validate_case_type_deprecate_restore_on_data_exports(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.case_type_deprecate() + data.validate_exports_edit_data_section(UserData.data_upload_path) + data.case_type_restore() + home.data_menu() + data.validate_exports_edit_data_section(UserData.data_upload_path) + +def test_case_14_verify_deprecate_cases_under_messaging_menu(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + menu = HomePage(driver, settings) + home.data_menu() + data.verify_data_page("N") + data.case_type_deprecate() + menu.messaging_menu() + data.verify_conditional_alert_under_messaging() + home.data_menu() + data.case_type_restore() + menu.messaging_menu() + data.verify_conditional_alert_under_messaging() + + +def test_case_15_validate_date_type_valid_values(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("Y") + data.verify_valid_values_date_type() + +def test_case_16_validate_multiple_choice_type_valid_values(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("Y") + data.verify_valid_values_multiple_choice_type() + +def test_case_17_validate_downloaded_valid_values(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.verify_file_getting_downloaded() + download_path = latest_download_file() + data.verify_excel_verification(download_path) + +def test_case_18_verify_uploading_updated_file(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.verify_file_getting_downloaded() + download_path = latest_download_file() + data.verify_update_excel(download_path) + data.verify_uploading_dd(download_path) + +def test_case_19_validate_invalid_valid_values(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("N") + data.verify_file_getting_downloaded() + download_path = latest_download_file() + data.verify_updating_excel_invalid_values(download_path) + data.verify_uploading_dd(download_path) + + +def test_case_20_verify_added_property_under_case_management_tab(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("Y") + data.verify_add_property_description() + home.applications_menu(UserData.application) + data.verify_case_management() + data.validating_app_summary() + +def test_case_21_verify_restore_case_property(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("Y") + data.verify_deprecate_restore_case_property("N") + home.applications_menu(UserData.application) + data.verify_case_management() + data.verify_warning_message() + home.data_menu() + data.verify_data_page("N") + data.verify_restore_case_property() + +def test_case_22_verify_case_list_explorer_report(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("Y") + property_value = data.add_new_case_property() + home.reports_menu() + data.view_case_list_explorer_report(property_value,'yes') + home.data_menu() + data.verify_data_page("Y") + data.case_property_deletion() + home.reports_menu() + data.view_case_list_explorer_report(property_value,'no') + +def test_case_23_verify_roles_permission_with_dd_access(driver,settings): + login = LoginPage(driver, settings["url"]) + menu = HomePage(driver, settings) + role = RolesPermissionPage(driver, settings) + web_user1 = WebUsersPage(driver) + data = DataDictionaryPage(driver) + login.logout() + login.login(settings["login_username"], settings["login_password"]) + menu.users_menu() + role.roles_menu_click() + print("Opened Roles and Permissions Page") + role_name1 = role.add_non_admin_role_dd(1) + print (role_name1) + menu.users_menu() + web_user1.edit_user_permission(role_name1) + login.logout() + login.login(UserData.p1p2_user, settings["login_password"]) + data.verify_data_dictionary_access_page() + login.logout() + login.login(settings["login_username"], settings["login_password"]) + menu.users_menu() + role.roles_menu_click() + print("Opened Roles and Permissions Page") + role_name1 = role.add_non_admin_role_dd(2) + print(role_name1) + time.sleep(2) + menu.users_menu() + web_user1.edit_user_permission(role_name1) + login.logout() + login.login(UserData.p1p2_user, settings["login_password"]) + data.verify_data_dictionary_access_page() + login.logout() + login.login(settings["login_username"], settings["login_password"]) + menu.users_menu() + role.roles_menu_click() + print("Opened Roles and Permissions Page") + role_name1 = role.add_non_admin_role_dd(3) + print(role_name1) + menu.users_menu() + web_user1.edit_user_permission(role_name1) + login.logout() + login.login(UserData.p1p2_user, settings["login_password"]) + data.verify_data_dictionary_revoke_access() + login.logout() + time.sleep(2) + login.login(settings["login_username"], settings["login_password"]) + menu.users_menu() + web_user1.edit_user_permission("Admin") + role.roles_menu_click() + role.delete_test_roles() + +def test_case_24_validate_case_importer_valid_values(driver,settings): + home = HomePage(driver, settings) + imp = ImportCasesPage(driver) + home.data_menu() + imp.replace_property_and_upload(UserData.case_type, UserData.file, "Yes", ['Hindi', 'Telugu','YYYY-MM-DD'], + ['select_dd_language', 'opened_date']) + + +def test_case_25_verify_making_a_new_version_for_deprecated_case_type(driver,settings): + home = HomePage(driver, settings) + data = DataDictionaryPage(driver) + home.data_menu() + data.verify_data_page("Y") + data.case_type_deprecate() + home.applications_menu(UserData.application) + data.validating_application() + #data.verify_case_data_page() + home.data_menu() + data.verify_data_page("Y") + + + + + + + + + + + diff --git a/Features/DataDictionary/testPages/__init__.py b/Features/DataDictionary/testPages/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Features/DataDictionary/testPages/data/__init__.py b/Features/DataDictionary/testPages/data/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Features/DataDictionary/testPages/data/data_dictionary_page.py b/Features/DataDictionary/testPages/data/data_dictionary_page.py new file mode 100644 index 000000000..314d40d73 --- /dev/null +++ b/Features/DataDictionary/testPages/data/data_dictionary_page.py @@ -0,0 +1,525 @@ +import os +import time +from time import sleep + +from matplotlib.widgets import EllipseSelector +from selenium.webdriver.common.by import By + +from ElasticSearchTests.testCases.conftest import settings +from Features.DataDictionary.userInputs.user_inputs import UserData +from HQSmokeTests.testCases.conftest import driver +from HQSmokeTests.testPages.home.home_page import HomePage +from common_utilities.Excel.excel_manage import ExcelManager +from common_utilities.selenium.base_page import BasePage +from common_utilities.generate_random_string import fetch_random_string +from common_utilities.path_settings import PathSettings +from selenium.webdriver.support.select import Select + +""""Contains test page elements and functions related to the Lookup Table module""" + + +class DataDictionaryPage(BasePage): + + def __init__(self, driver): + super().__init__(driver) + + #data dictionary page + self.view_data = (By.XPATH, " //*[@id='ProjectDataTab']") + self.data = (By.LINK_TEXT, "Data") + self.view_all = (By.LINK_TEXT, "View All") + self.data_dictionary = (By.XPATH, "//*[@id='hq-sidebar']/nav/ul[4]/li/a") + self.dd = (By.XPATH, "//*[@class='text-hq-nav-header']") + self.check = (By.XPATH, "//*[@id='download-dict']") + self.datatype = (By.XPATH, "(//select[@class='form-control'])[5]") + self.dd_language =(By.XPATH,"(//select[@class='form-control'])[1]") + self.upload = (By.XPATH, "//*[@id='gtm-upload-dict']") + self.choose_file_text_field = (By.ID, "file") + self.upload_button = (By.XPATH, "//*[@class='btn btn-primary disable-on-submit']") + self.menu_settings = (By.XPATH, "//a[@class='appnav-title appnav-title-secondary appnav-responsive']") + self.type_value = (By.XPATH, "//*[@id='case_type']") + self.save = (By.XPATH, "//*[@class='pull-right savebtn-bar savebtn-bar-save']") + self.case_type_value = (By.XPATH, "//*[@href='#case_dd']") + self.case_property = (By.XPATH, "(//div[@id='data-dictionary-table']//div[1]//div[4]//select[1])") + self.case_property_vv = (By.XPATH,"//div[@class='groups ui-sortable']//div[2]//div[4]//select[1]") + self.description = (By.XPATH, "//div[@class='groups ui-sortable']//div//div[1]//div[5]//textarea[1]") + self.add_property = (By.XPATH, "//*[@id='data-dictionary-table']/div[2]/div[1]/div[4]/div/form/input") + self.add_group = (By.XPATH, "//input[@placeholder='Group Name']") + self.add_property_button = (By.XPATH, "//*[@id='gtm-add-case-property']") + self.added_group = (By.XPATH, "//*[@id='data-dictionary-table']/div[2]/div[1]/div[2]/div[5]/textarea") + self.deprecate_button = (By.XPATH, "(//*[@id='gtm-deprecate-case-property'])[1]") + self.show_deprecate = (By.XPATH, "//*[@data-bind='click: $root.showDeprecated, visible: !showAll()']") + self.restore_button = (By.XPATH, "//button[@title='Restore Property']") + self.hide_deprecate = (By.XPATH, "//*[@data-bind='click: $root.hideDeprecated, visible: showAll']") + self.deprecate_case = (By.XPATH, "//*[@id='hq-content']/div[2]/div[3]/div/a[3]") + self.confirm = (By.XPATH, "//*[@id='gtm-deprecate-case-type-confirm']") + self.show_deprecate_case_type = (By.XPATH, "//*[@class='deprecate-case-type']") + self.add_group_button = (By.XPATH, "//*[@id='gtm-add-case-property-group']") + self.data_dictionary = (By.LINK_TEXT, "Data Dictionary") + self.delete_case_property = (By.XPATH, "(//button[@title='Delete Property'][normalize-space()='Delete'])[1]") + self.delete_property_confirm = (By.XPATH, "//button[@id='delete-case-prop-btn']") + self.show_deprecated_case_type = (By.XPATH, "//*[@class='deprecate-case-type']") + self.restore_case_type = (By.XPATH, "(//*[@class='btn btn-default'])[3]") + self.restore_case_type_new =( By.XPATH,"//button[@title='Restore Property']") + self.date_valid_values = (By.XPATH, + "//*[@id='data-dictionary-table']/div[2]/div[1]/div[3]/div[1]/div[6]/div[2]") + self.edit_button = (By.XPATH, "//div[@id='data-dictionary-table']//div[1]//div[6]//div[1]//div[1]//div[1]//a[1]") + self.edit_button_vv = (By.XPATH,"//div[@id='data-dictionary-table']//div[1]//div[6]//div[1]//div[1]//div[1]//a[1]") + self.add_item = (By.XPATH, "(//a[@data-enum-action='add'])[3]") + self.valid_value_text = (By.XPATH, "(//input[@placeholder='valid value'])") + self.valid_description = (By.XPATH, "(//input[@placeholder='description'])") + self.done = (By.XPATH, "(//button[@class='btn btn-primary'][normalize-space()='Done'])[1]") + self.property_deprecate = (By.XPATH, "(//*[@id='gtm-deprecate-case-property'])[5]") + self.property_deprecate_message = (By.XPATH, "(//*[contains(text(),'Property has been deprecated')])[3]") + self.data_bold = (By.XPATH, "//*[@id='hq-breadcrumbs']/li[1]/a/strong") + self.property_description = (By.XPATH, "//div[@class='groups ui-sortable']//div//div[5]//div[5]//textarea[1]") + self.add_property_values = "//div[@class='atwho-view'][not(contains(@style,'none'))]//li//strong[.='{}'] " + self.delete_button_vv = (By.XPATH,"//a[@data-enum-action='remove']") + self.upload_error_message = (By.XPATH, + "//div[@id='hq-messages-container']/div[@class='row']/div[@class='col-sm-12']/div[@class='alert alert-margin-top fade in html alert-danger']") + + #In App + self.make_new_version_button = (By.XPATH, "//button[contains(@data-bind,'Make New Version')]") + self.case_list = (By.XPATH, "//*[@id='hq-sidebar']/nav/ul[1]/li/div/a[2]") + self.case_type_warning = (By.XPATH, "//*[@id='deprecated-case-types-warning']") + self.applications_menu_id = (By.ID, "ApplicationsTab") + self.case_list_warning = (By.XPATH, "//*[@id='case_type_deprecated_warning']") + self.app_description = (By.XPATH, + "//*[@id='js-appmanager-body']/div[3]/inline-edit/div/div/div[2]/div[1]/textarea") + self.edit_icon = (By.XPATH, "//*[@id='js-appmanager-body']/div[3]/inline-edit/div/div/div[1]/span[4]/i") + self.save_description = (By.XPATH, + "//*[@id='js-appmanager-body']/div[3]/inline-edit/div/div/div[2]/div[3]/button[1]") + self.case_data_page_warning = (By.XPATH, "//*[@class='alert alert-warning']") + self.registration_form = (By.XPATH, "(//*[@data-category='App Builder'])[1]") + self.settings_icon = (By.XPATH, "(//*[@data-category='App Builder'])[2]") + self.description_text = (By.XPATH, "(//*[@class='read-only'])[4]") + self.app_summary_button = (By.XPATH, + "//*[@class='appmanager-page-actions']/a/i[@class = 'fa-regular fa-rectangle-list']") + self.case_summary_button = (By.XPATH, "//*[@class='fcc fcc-fd-external-case appnav-primary-icon']") + self.description_property = (By.XPATH, "(//*[@data-bind='text: $parent.description'])[2]") + + # Reports + self.case_list_explorer_report = (By.LINK_TEXT, "Case List Explorer") + self.report_case_type = (By.XPATH, "//select[contains(@id,'report_filter_case_type')]") + + #Exports + self.export_case_data_link = (By.LINK_TEXT, 'Export Case Data') + self.add_export_button = (By.XPATH, "//a[@href='#createExportOptionsModal']") + self.case_type_dropdown = (By.XPATH, "//select[@name='case_type']") + self.daily_saved_exports_link = (By.LINK_TEXT, 'Daily Saved Exports') + self.close_popup = (By.XPATH, "//*[@id='createExportOptionsModal']/div/form/div/div[1]/button") + self.model_type = (By.ID, "id_model_type") + self.export_excel_dash_int = (By.LINK_TEXT, 'Excel Dashboard Integration') + self.powerBI_tab_int = (By.LINK_TEXT, 'PowerBi/Tableau Integration') + self.add_export_conf = (By.XPATH, "//button[@data-bind='visible: showSubmit, disable: disableSubmit']") + self.export_settings_create = (By.XPATH, "//button[@class='btn btn-lg btn-primary']") + self.warning_label = (By.XPATH, "//*[@class='badge text-bg-warning']") + self.case_type_dropdown1 = (By.XPATH, + "//label[.='Case Type']//following-sibling::div/select[@name='case_type']") + self.copy_cases_menu = (By.LINK_TEXT, "Copy Cases") + self.reassign_cases_menu = (By.LINK_TEXT, "Reassign Cases") + self.reassign_case_type = (By.ID, "report_filter_case_type") + self.deduplicate_case_link = (By.LINK_TEXT, 'Deduplicate Cases') + self.add_rule_button = (By.ID, 'add-new') + self.add_rule_name = (By.XPATH, "//input[@type='text']") + self.deduplicate_case_type = (By.XPATH, "//select[@name='case_type']") + self.import_cases_menu = (By.LINK_TEXT, "Import Cases from Excel") + self.choose_file = (By.XPATH,"//input[@id='id_bulk_upload_file']") + self.next_step = (By.XPATH, "//button[normalize-space()='Next step']") + + # Messaging + self.cond_alerts = (By.LINK_TEXT, "Conditional Alerts") + self.add_cond_alert = (By.LINK_TEXT, "New Conditional Alert") + self.messaging_menu_id = (By.ID, "MessagingTab") + self.case_type_ca = (By.XPATH, "//select[contains(@name,'case_type')]") + self.cond_alert_name_input = "cond_alert_" + fetch_random_string() + self.cond_alert_name = (By.XPATH, "//input[@name='conditional-alert-name']") + self.continue_button_basic_tab = ( + By.XPATH, "//button[@data-bind='click: handleBasicNavContinue, enable: basicTabValid']") + #CLE + self.property_value = (By.XPATH, "//div[@id='data-dictionary-table']//div[1]//div[3]//div[1]//div[3]//input[1]") + self.edit_column = (By.XPATH, + "//div[./label[contains(.,'Columns')]]//following-sibling::div//a[@data-parent='#case-list-explorer-columns']") + self.properties_table = (By.XPATH, "//tbody[contains(@data-bind,'properties')]") + self.add_property_button_cle = (By.XPATH, "//button[normalize-space()='Add Property']") + self.property_name_input = (By.XPATH, "(//tbody[contains(@data-bind,'properties')]//td[2]//input)[last()]") + self.apply_id = (By.ID, "apply-filters") + self.add_property_column = (By.XPATH, "(//table[contains(@class,'datatable')]//th[5])[1]") + + + def verify_data_page(self,flag): + self.js_click(self.data_dictionary, 5) + if self.is_present_and_displayed(self.case_type_value): + assert self.is_present_and_displayed(self.case_type_value, 2) + print("case-dd page is opened") + else: + self.case_type_restore() + if flag =='Y': + self.wait_to_click(self.case_type_value) + + def verify_dropdown_values(self): + self.wait_to_click(self.datatype) + dropdown_values = self.find_elements_texts(self.datatype) + print(dropdown_values) + dt = ['Select a data type\nDate\nPlain\nNumber\nMultiple Choice\nBarcode\nGPS\nPhone Number\nPassword'] + assert dropdown_values == dt + print("Below are the dropdown values") + for data_type in dropdown_values: + print(data_type) + + def verify_file_getting_downloaded(self): + self.wait_to_click(self.check, 2) + print("File is downloaded") + time.sleep(5) + + + def verify_uploading_dd(self, path): + download_path = str(PathSettings.DOWNLOAD_PATH / path) + self.wait_to_click(self.upload, 2) + self.send_keys(self.choose_file, download_path) + self.wait_to_click(self.upload_button, 2) + if self.is_present_and_displayed(self.upload_error_message): + print("Error in uploading file") + else: + print("File is upload") + + def edit_case_property_description(self): + self.wait_to_click(self.case_type_value, 10) + self.wait_to_click(self.description) + self.wait_to_clear_and_send_keys(self.description, "property description to be tested" + fetch_random_string()) + self.wait_to_click(self.datatype) + self.wait_to_click(self.save) + print("editing property description updated") + + def add_new_case_property(self): + self.wait_to_click(self.case_type_value, 5) + self.wait_to_click(self.add_property) + self.send_keys(self.add_property, UserData.case_properties) + self.wait_to_click(self.add_property_button) + self.wait_to_click(self.save) + value_text = self.wait_to_get_value(self.property_value) + print("new property added :", value_text) + return value_text + + def view_case_list_explorer_report(self, value1, flag): + self.wait_to_click(self.case_list_explorer_report) + time.sleep(30) + self.wait_for_element(self.edit_column) + self.wait_to_click(self.edit_column,10) + self.wait_for_element(self.properties_table) + self.wait_to_click(self.add_property_button_cle) + self.wait_to_click(self.property_name_input) + self.send_keys(self.property_name_input, value1) + time.sleep(0.5) + if flag == "yes": + assert self.is_present( + (By.XPATH, self.add_property_values.format(value1))), 'entered property is not displayed' + print("entered property displayed on the dropdown") + else: + assert not self.is_present( + (By.XPATH, self.add_property_values.format(value1))), "Entered property is displayed" + print("added property is deleted on data dictionary page") + self.wait_to_click(self.apply_id) + time.sleep(10) + self.scroll_to_bottom() + self.is_present_and_displayed(self.add_property_column) + property_added = self.get_text(self.add_property_column) + assert property_added == value1 + print("Added property displayed on the CLE report result page") + + def case_property_deletion(self): + self.reload_page() + self.wait_to_click(self.delete_case_property) + self.wait_to_click(self.delete_property_confirm) + self.wait_to_click(self.save) + print(" added property deleted") + + def adding_a_new_group(self): + self.wait_to_click(self.case_type_value, 10) + self.wait_to_click(self.add_group) + self.wait_to_clear_and_send_keys(self.add_group, UserData.name_group) + added_group_name = self.wait_to_get_value(self.add_group) + self.wait_to_click(self.add_group_button, 10) + self.wait_to_click(self.save, 10) + self.is_present_and_displayed(self.added_group, 10) + print(added_group_name,"group added successfully") + + def updating_group_description(self): + self.wait_to_click(self.case_type_value, 10) + self.wait_to_clear_and_send_keys(self.added_group, "updated_description_value") + self.wait_to_click(self.add_group) + self.wait_to_click(self.save, 10) + print("group description is updated") + + def deprecating_property(self): + self.wait_to_click(self.case_type_value, 2) + self.wait_to_click(self.deprecate_button, 2) + self.wait_to_click(self.save, 5) + self.reload_page() + self.js_click(self.show_deprecate, 2) + self.is_present_and_displayed(self.restore_button) + print("Restore button is displayed") + self.is_present_and_displayed(self.hide_deprecate) + print("hide deprecate button is displayed") + self.wait_to_click(self.restore_button) + self.wait_to_click(self.save, 2) + print("Case property is restored") + + def case_type_deprecate(self): + self.wait_to_click(self.case_type_value, 10) + self.wait_to_click(self.deprecate_case, 10) + self.wait_to_click(self.confirm, 10) + self.wait_to_click(self.show_deprecate_case_type, 30) + print("case type has been deprecated") + self.wait_to_click(self.data_bold) + + def case_type_restore(self): + self.wait_to_click(self.data_bold,10) + self.js_click(self.data_dictionary,10) + self.wait_to_click(self.show_deprecated_case_type,30) + self.wait_to_click(self.case_type_value) + self.wait_to_click(self.restore_case_type) + print("case type has been restored") + + def validating_application(self): + self.wait_to_click(self.edit_icon) + self.wait_to_clear_and_send_keys(self.app_description, UserData.application_description) + self.wait_to_click(self.save_description) + self.wait_to_click(self.make_new_version_button, 10) + self.is_present_and_displayed(self.case_type_warning, 10) + print("The new application build contains the following deprecated case type") + self.js_click(self.case_list, 20) + self.is_present_and_displayed(self.case_list_warning, 10) + print("This case type has been deprecated in the Data Dictionary on case list page") + + def validating_app_summary(self): + self.wait_to_click(self.app_summary_button) + self.wait_to_click(self.case_summary_button) + self.wait_to_click(self.description_property) + property_description_value = self.get_text(self.description_property) + assert property_description_value == 'Testing the age property description' + print("Descriptions of each property automatically show in the App Summary page.") + + def verify_case_data_page(self): + self.get_url(UserData.case_data_link) + self.is_present_and_displayed(self.case_data_page_warning) + print("This case uses a deprecated case type. See the help documentation for more information is displayed") + time.sleep(20) + + + def verify_reports(self): + time.sleep(50) + self.wait_to_click(self.case_list_explorer_report, 20) + self.wait_for_element(self.report_case_type, 60) + dropdown = self.get_all_dropdown_options(self.report_case_type) + if 'case_dd' in dropdown: + print("Active case types are displayed in the reports.") + else: + print("deprecated case types are not displayed in the reports.") + + def verify_exports(self): + self.wait_to_click(self.export_case_data_link, 10) + self.wait_for_element(self.add_export_button, 200) + dropdown = self.get_all_dropdown_options(self.case_type_dropdown) + if 'case_dd' in dropdown: + print("Active case types are displayed in the case exports.") + else: + print("deprecated case types are not displayed in the case exports.") + #self.wait_to_click(self.close_popup) + self.wait_to_click(self.daily_saved_exports_link) + self.wait_to_click(self.add_export_button, 30) + self.is_visible_and_displayed(self.model_type, 200) + self.wait_for_element(self.model_type, 400) + self.select_by_value(self.model_type, UserData.model_value) + dropdown = self.get_all_dropdown_options(self.case_type_dropdown) + if 'case_dd' in dropdown: + print("Active case types are displayed in the daily saved exports.") + else: + print("deprecated case types are not displayed in the daily saved exports.") + self.wait_to_click(self.export_excel_dash_int) + self.wait_to_click(self.add_export_button,10) + self.wait_to_click(self.model_type,60) + self.select_by_value(self.model_type, UserData.model_value) + dropdown = self.get_all_dropdown_options(self.case_type_dropdown) + if 'case_dd' in dropdown: + print("Active case types are displayed in the excel dashboard exports.") + else: + print("deprecated case types are not displayed in the excel dashboard exports.") + #self.wait_to_click(self.close_popup) + self.wait_to_click(self.powerBI_tab_int) + self.wait_to_click(self.add_export_button,20) + self.wait_to_click(self.model_type,200) + self.select_by_value(self.model_type, UserData.model_value) + dropdown = self.get_all_dropdown_options(self.case_type_dropdown) + if 'case_dd' in dropdown: + print("Active case types are displayed in the power bi exports.") + else: + print("deprecated case types are not displayed in the power bi exports.") + + def create_case_export(self): + self.wait_to_click(self.data_bold) + self.wait_to_click(self.export_case_data_link, 10) + self.wait_to_click(self.add_export_button, 200) + self.is_visible_and_displayed(self.case_type_dropdown, 200) + self.wait_for_element(self.case_type_dropdown, 200) + self.select_by_text(self.case_type_dropdown, UserData.case_type) + self.wait_to_click(self.add_export_conf) + self.wait_to_click(self.export_settings_create) + print("Export created!!") + + def validate_exports(self): + self.wait_to_click(self.export_case_data_link, 10) + self.is_present_and_displayed(self.warning_label) + print("deprecated case type label displayed on the already created export") + + def validate_exports_edit_data_section(self, filename): + self.wait_for_element(self.data_bold, 10) + self.js_click(self.data_bold,10) + self.wait_to_click(self.copy_cases_menu, 200) + self.wait_for_element(self.case_type_dropdown1, 200) + dropdown = self.get_all_dropdown_options(self.case_type_dropdown1) + if 'case_dd' in dropdown: + print("Active case types are displayed in the copy cases page.") + else: + print("deprecated case types are not displayed in the copy cases page.") + self.wait_to_click(self.reassign_cases_menu, 100) + dropdown = self.get_all_dropdown_options(self.reassign_case_type) + if 'case_dd' in dropdown: + print("Active case types are displayed in the reassign cases page.") + else: + print("deprecated case types are not displayed in the reassign cases page.") + self.wait_to_click(self.deduplicate_case_link, 100) + self.wait_to_click(self.add_rule_button) + self.send_keys(self.add_rule_name, 'deduplicate_rule_1') + dropdown = self.get_all_dropdown_options(self.deduplicate_case_type) + if 'case_dd' in dropdown: + print("Active case types are displayed in the deduplicate page.") + else: + print("deprecated case types are not displayed in the deduplicate page.") + self.wait_to_click(self.import_cases_menu, 50) + time.sleep(5) + filepath = os.path.abspath(os.path.join(UserData.user_input_base_dir, str(filename))) + # filepath = str(UserData.user_input_base_dir + "\\" + filepath) + print("File Path: ", filepath) + self.wait_for_element(self.choose_file_text_field) + print("File Path: ", filepath) + self.send_keys(self.choose_file_text_field, filepath) + self.wait_for_element(self.next_step) + self.js_click(self.next_step) + self.is_visible_and_displayed(self.case_type_ca) + dropdown = self.get_all_dropdown_options(self.case_type_ca) + if 'case_dd' in dropdown: + print("Active case types are displayed in the import cases from excel page.") + else: + print("deprecated case types are not displayed in the import cases from excel page.") + self.js_click(self.data_dictionary) + + + def verify_conditional_alert_under_messaging(self): + self.wait_to_click(self.cond_alerts) + self.wait_to_click(self.add_cond_alert) + self.send_keys(self.cond_alert_name, self.cond_alert_name_input) + self.wait_to_click(self.continue_button_basic_tab) + time.sleep(10) + self.wait_to_click(self.case_type_ca, 10) + dropdown = self.get_all_dropdown_options(self.case_type_ca) + if 'case_dd' in dropdown: + print("Active case types are displayed in the conditional alert page.") + else: + print("deprecated case types are not displayed in the conditional alert page.") + + def verify_valid_values_date_type(self): + self.select_by_text(self.case_property_vv,'Date') + self.is_present_and_displayed(self.date_valid_values) + print("YYYY-MM-DD Valid values are getting displayed") + self.wait_to_click(self.case_property_vv) + self.wait_to_click(self.save) + + def verify_valid_values_multiple_choice_type(self): + self.select_by_text(self.dd_language, 'Multiple Choice') + self.wait_to_click(self.edit_button_vv) + self.wait_to_click(self.valid_value_text, 2) + self.wait_to_clear_and_send_keys(self.valid_value_text, UserData.english_value) + self.wait_to_click(self.valid_description, 2) + self.wait_to_clear_and_send_keys(self.valid_description, UserData.english_value) + self.js_click(self.done) + + + def verify_resetting_valid_values(self): + self.wait_to_click(self.edit_button) + self.wait_to_click(self.delete_button_vv) + self.select_by_text(self.case_property,'Plain') + self.wait_to_click(self.save) + + def verify_excel_verification(self, download_path): + download_path = str(PathSettings.DOWNLOAD_PATH / download_path) + time.sleep(5) + excel = ExcelManager() + excel.read_excel('case_dd-vl', download_path) + print("The valid values are displayed") + + def verify_update_excel(self, download_path): + download_path = str(PathSettings.DOWNLOAD_PATH / download_path) + excel = ExcelManager() + excel.write_excel_data('case_dd', 2, 6, UserData.plain1, download_path) + excel.write_excel_data('case_dd', 4, 6, UserData.plain1, download_path) + excel.write_excel_data('case_dd', 3, 6, UserData.lookup_function, download_path) + + def verify_updating_excel_invalid_values(self, download_path1): + download_path1 = str(PathSettings.DOWNLOAD_PATH / download_path1) + excel = ExcelManager() + excel.write_excel_data('case_dd-vl', 2, 2, UserData.randomvalue1, download_path1) + excel.write_excel_data('case_dd-vl', 2, 3, UserData.randomvalue1, download_path1) + + def verify_add_property_description(self): + self.wait_to_click(self.property_description) + self.wait_to_clear_and_send_keys(self.property_description, UserData.age_property_description) + self.get_text(self.property_description) + self.wait_to_click(self.datatype) + self.wait_to_click(self.save) + + def verify_case_management(self): + self.wait_to_click(self.registration_form) + self.wait_to_click(self.settings_icon) + description = self.get_text(self.description_text) + assert description == UserData.age_property_description + + def verify_deprecate_restore_case_property(self,flag): + self.wait_to_click(self.property_deprecate) + self.wait_to_click(self.save) + time.sleep(5) + self.wait_to_click(self.show_deprecate) + assert self.is_present_and_displayed(self.restore_case_type_new), "case not deprecated" + print("case type is deprecated") + if flag =="Y": + self.wait_to_click(self.restore_case_type_new) + self.wait_to_click(self.save) + print("deprecated property restored") + + def verify_warning_message(self): + sleep(10) + self.is_present_and_displayed(self.property_deprecate_message) + message = self.get_text(self.property_deprecate_message) + print(message) + + def verify_restore_case_property(self): + self.wait_to_click(self.case_type_value) + self.js_click(self.show_deprecate, 2) + self.wait_to_click(self.restore_button) + self.wait_to_click(self.save, 2) + print("deprecated Case property is restored") + + def verify_data_dictionary_access_page(self): + self.wait_to_click(self.view_data, 2) + self.wait_to_click(self.data_dictionary, 2) + self.wait_to_click(self.case_type_value) + if self.is_present_and_displayed(self.upload_button): + print("both view and edit access is present for the user login") + else: + print("only view access is present for the user login") + + def verify_data_dictionary_revoke_access(self): + self.wait_to_click(self.view_data, 2) + if self.is_present_and_displayed(self.data_dictionary): + print("access not revoked") + else: + print("Access revoked") diff --git a/Features/DataDictionary/userInputs/__init__.py b/Features/DataDictionary/userInputs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Features/DataDictionary/userInputs/case_dd.xlsx b/Features/DataDictionary/userInputs/case_dd.xlsx new file mode 100644 index 000000000..beebfc550 Binary files /dev/null and b/Features/DataDictionary/userInputs/case_dd.xlsx differ diff --git a/Features/DataDictionary/userInputs/import_file.xlsx b/Features/DataDictionary/userInputs/import_file.xlsx new file mode 100644 index 000000000..98b42c7d5 Binary files /dev/null and b/Features/DataDictionary/userInputs/import_file.xlsx differ diff --git a/Features/DataDictionary/userInputs/user_inputs.py b/Features/DataDictionary/userInputs/user_inputs.py new file mode 100644 index 000000000..2532091c2 --- /dev/null +++ b/Features/DataDictionary/userInputs/user_inputs.py @@ -0,0 +1,32 @@ +""""Contains test data that are used as user inputs across various areasn in CCHQ""" +import os +import random +import string + +from common_utilities.generate_random_string import fetch_random_string +from common_utilities.path_settings import PathSettings + + +class UserData: + + + """User Test Data""" + user_input_base_dir = os.path.dirname(os.path.abspath(__file__)) + application = "Data_Dictionary" + application_description = 'dd' + str(fetch_random_string()) + case_properties = 'property'+ str(fetch_random_string()) + name_group = 'group'+ str(fetch_random_string()) + case_type = 'case_dd' + #case_data_link = 'https://staging.commcarehq.org/a/qa-automation/reports/case_data/d57fa5c7fb184d219e7fe155dabdbb6a/' + english_value ='English' + plain1 ='Plain' + randomvalue1 = 'English' + date1 = 'Date' + number ='Number' + updated_input = 'English' + age_property_description = 'Testing the age property description' + model_value = 'case' + data_upload_path = "import_file.xlsx" + p1p2_user = "p1p2.web.user@gmail.com" + file = os.path.abspath(os.path.join(user_input_base_dir, "case_dd.xlsx")) + lookup_function = '=(F2)' diff --git a/Features/Lookuptable/requires.txt b/Features/Lookuptable/requires.txt index c144a0e26..1fb1d7849 100644 --- a/Features/Lookuptable/requires.txt +++ b/Features/Lookuptable/requires.txt @@ -12,6 +12,4 @@ pytest-xdist[psutil] pyotp >=2.6.0 pytest-order py -beautifulsoup4 -pytest-metadata -matplotlib \ No newline at end of file +beautifulsoup4 \ No newline at end of file diff --git a/Features/Powerbi_integration_exports/README.md b/Features/Powerbi_integration_exports/README.md new file mode 100644 index 000000000..6e43f04c9 --- /dev/null +++ b/Features/Powerbi_integration_exports/README.md @@ -0,0 +1,57 @@ +## Commcare Powerbi Integration exports Test Script + +These tests ensure that the [Powerbi_integration_exports] (https://dimagi.atlassian.net/wiki/spaces/commcarepublic/pages/2143945995/Microsoft+Power+BI+Integration)features work as expected and that there are no regressions +The automated tests comprises of [these powerbi_integration_exports functionality.] (https://docs.google.com/spreadsheets/d/1wlW7gvmz8aW1gPLurHgD5onU1_C6laUhUtzQfmi8H9Y/edit?gid=0#gid=0) +## Executing Scripts + +### On Local Machine + +#### Setting up test environment + +```sh + +# create and activate a virtualenv using your preferred method. Example: +python -m venv venv +source venv/bin/activate + + +# install requirements +pip install -r requires.txt + +``` + +[More on setting up virtual environments](https://confluence.dimagi.com/display/GTD/QA+and+Python+Virtual+Environments) + + +#### Running Tests + + + - Copy `settings-sample.cfg` to `settings.cfg` and populate `settings.cfg` for +the environment you want to test. +- Run tests using pytest command like: + +```sh + +# To execute all the test cases +pytest -v --rootdir= Features/Lookuptable/testCases + +``` +- You could also pass the following arguments + - ` -n 3 --dist=loadfile` - This will run the tests parallelly in 3 instances. The number of reruns is configurable. + - ` --reruns 1` - This will re-run the tests once in case of failures.The number of reruns is configurable too. + +### Trigger Manually on Gitaction + +clone this repository + +To manually trigger the script, + - Go to [Powerbi_integration_exports action](https://github.com/dimagi/dimagi-qa/actions/lookuptable.yml) + - Run workflow + - Use workflow from ```master``` + - Run! + +If you are a part of the QA team, you'll receive emails for the result of the run after it's complete. + +clone this repository + +Besides, you should be able to find the zipped results in the **Artifacts** section, of the corresponding run (after it's complete). diff --git a/Features/Powerbi_integration_exports/__init__.py b/Features/Powerbi_integration_exports/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Features/Powerbi_integration_exports/requires.txt b/Features/Powerbi_integration_exports/requires.txt new file mode 100644 index 000000000..0fc230cfa --- /dev/null +++ b/Features/Powerbi_integration_exports/requires.txt @@ -0,0 +1,22 @@ +## Stores information about all the libraries, modules, and packages that are used in this project. + + +flake8>=3.8.4 +pandas>=1.2.2 +pytest +py>=1.10.0 +pytest-html>=3.1.1 +pytest-json-report +selenium == 4.11.0 +openpyxl +matplotlib >= 3.3.4 +pytest-rerunfailures +pytest-xdist +pytest-xdist[psutil] +pytest-order +requests +imap-tools +beautifulsoup4 +html5lib +pytest-metadata +pyotp >=2.6.0 \ No newline at end of file diff --git a/Features/Powerbi_integration_exports/settings-sample.cfg b/Features/Powerbi_integration_exports/settings-sample.cfg new file mode 100644 index 000000000..b6bb402bb --- /dev/null +++ b/Features/Powerbi_integration_exports/settings-sample.cfg @@ -0,0 +1,12 @@ +[default] +# This is the environment url of commcare +url = https://www.commcarehq.org/ +# Login username of the webuser +login_username = +# Login password of the webuser +login_password = +# This is a preconfigured authentication key used for 2FA tests on staging - If 2FA enabled on staging. +staging_auth_key = +# This is a preconfigured authentication key used for 2FA tests on prod +prod_auth_key = + diff --git a/Features/Powerbi_integration_exports/testCases/__init__.py b/Features/Powerbi_integration_exports/testCases/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Features/Powerbi_integration_exports/testCases/conftest.py b/Features/Powerbi_integration_exports/testCases/conftest.py new file mode 100644 index 000000000..4ba1337f7 --- /dev/null +++ b/Features/Powerbi_integration_exports/testCases/conftest.py @@ -0,0 +1,295 @@ +import os + +from configparser import ConfigParser +from pathlib import Path +from common_utilities.fixtures import * + +""""This file provides fixture functions for driver initialization""" + +global driver + + +@pytest.fixture(scope="session") +def environment_settings_lookup(): + """Load settings from os.environ + + Names of environment variables: + DIMAGIQA_URL + DIMAGIQA_LOGIN_USERNAME + DIMAGIQA_LOGIN_PASSWORD + DIMAGIQA_MAIL_USERNAME + DIMAGIQA_MAIL_PASSWORD + + See https://docs.github.com/en/actions/reference/encrypted-secrets + for instructions on how to set them. + """ + settings = {} + for name in ["url", "login_username", "login_password", "staging_auth_key", "prod_auth_key"]: + + var = f"DIMAGIQA_{name.upper()}" + if var in os.environ: + settings[name] = os.environ[var] + if "url" not in settings: + env = os.environ.get("DIMAGIQA_ENV") or "staging" + subdomain = "www" if env == "production" else env + # updates the url with the project domain while testing in CI + project = "a/qa-automation-prod" if env == "production" else "a/qa-automation" + settings["url"] = f"https://{subdomain}.commcarehq.org/{project}" + return settings + + +@pytest.fixture(scope="session", autouse=True) +def settings(environment_settings_lookup): + if os.environ.get("CI") == "true": + settings = environment_settings_lookup + settings["CI"] = "true" + if any(x not in settings for x in ["url", "login_username", "login_password", + "staging_auth_key", "prod_auth_key"]): + lines = environment_settings_lookup.__doc__.splitlines() + vars_ = "\n ".join(line.strip() for line in lines if "DIMAGIQA_" in line) + raise RuntimeError( + f"Environment variables not set:\n {vars_}\n\n" + "See https://docs.github.com/en/actions/reference/encrypted-secrets " + "for instructions on how to set them." + ) + return settings + path = Path(__file__).parent.parent / "settings.cfg" + if not path.exists(): + raise RuntimeError( + f"Not found: {path}\n\n" + "Copy settings-sample.cfg to settings.cfg and populate " + "it with values for the environment you want to test." + ) + settings = ConfigParser() + settings.read(path) + # updates the url with the project domain while testing in local + if settings["default"]["url"] == "https://www.commcarehq.org/": + settings["default"]["url"] = f"{settings['default']['url']}a/qa-automation-prod" + else: + settings["default"]["url"] = f"{settings['default']['url']}a/qa-automation" + return settings["default"] + +def pytest_terminal_summary(terminalreporter, exitstatus, config): + # Collect test counts + passed = terminalreporter.stats.get('passed', []) + failed = terminalreporter.stats.get('failed', []) + error = terminalreporter.stats.get('error', []) + skipped = terminalreporter.stats.get('skipped', []) + xfail = terminalreporter.stats.get('xfail', []) + + env = os.environ.get("DIMAGIQA_ENV", "default_env") + + # Define the filename based on the environment + filename = f'powerbi_test_counts_{env}.txt' + + # Write the counts to a file + with open(filename, 'w') as f: + f.write(f'PASSED={len(passed)}\n') + f.write(f'FAILED={len(failed)}\n') + f.write(f'ERROR={len(error)}\n') + f.write(f'SKIPPED={len(skipped)}\n') + f.write(f'XFAIL={len(xfail)}\n') + +# conftest.py +import pytest +import matplotlib.pyplot as plt +import base64 +from io import BytesIO + +_test_stats = {} + +def pytest_sessionfinish(session, exitstatus): + """Collect stats at the end of the test session.""" + tr = session.config.pluginmanager.get_plugin("terminalreporter") + global _test_stats + _test_stats = { + "passed": len(tr.stats.get("passed", [])), + "failed": len(tr.stats.get("failed", [])), + "skipped": len(tr.stats.get("skipped", [])), + "error": len(tr.stats.get("error", [])), + "xfail": len(tr.stats.get("xfail", [])), + "reruns": len(tr.stats.get("rerun", [])), + } + save_summary_charts(_test_stats) + +import base64 + +def save_summary_charts(stats): + from pathlib import Path + out_dir = Path("slack_charts") + out_dir.mkdir(exist_ok=True) + + passed = stats.get("passed", 0) + failed = stats.get("failed", 0) + skipped = stats.get("skipped", 0) + reruns = stats.get("reruns", 0) + + # --- Pie chart with legend --- + pie_labels = ["Passed", "Failed", "Skipped"] + pie_sizes = [passed, failed, skipped] + pie_colors = ["#66bb6a", "#ef5350", "#fad000"] + + fig, ax = plt.subplots() + wedges, texts = ax.pie( + pie_sizes, + labels=None, + colors=pie_colors, + startangle=90, + wedgeprops=dict(width=0.4) + ) + ax.axis("equal") + ax.set_title("Test Summary") + + # Add legend with counts + ax.legend( + [f"Passed: {passed}", f"Failed: {failed}", f"Skipped: {skipped}"], + loc="lower center", + ncol=3, + bbox_to_anchor=(0.5, -0.15) + ) + + fig.savefig(out_dir / "summary_pie.png", bbox_inches="tight") + plt.close(fig) + + # --- Bar chart with labels + legend --- + bar_path = None + if failed > 0 or reruns > 0: # ✅ only generate if needed + fig, ax = plt.subplots() + bars = ax.bar( + ["Failed", "Reruns"], + [failed, reruns], + color=["#ef5350", "#ffa726"] + ) + ax.set_ylabel("Number of Tests") + ax.set_title("Failures and Reruns") + + # Add counts above bars + for bar in bars: + height = bar.get_height() + ax.text( + bar.get_x() + bar.get_width() / 2, + height + 0.05, + str(int(height)), + ha="center", + va="bottom", + fontsize=10, + fontweight="bold" + ) + + # Legend with counts + ax.legend( + [f"Failed: {failed}", f"Reruns: {reruns}"], + loc="lower center", + ncol=2, + bbox_to_anchor=(0.5, -0.15) + ) + + bar_path = out_dir / "summary_bar.png" + fig.savefig(bar_path, bbox_inches="tight") + plt.close(fig) + + # --- Combine --- + combine_charts( + pie_path=out_dir / "summary_pie.png", + bar_path=bar_path, + combined_path=out_dir / "summary_combined.png" + ) + + +import matplotlib.pyplot as plt +from PIL import Image + +def combine_charts(pie_path="slack_charts/summary_pie.png", + bar_path=None, + combined_path="slack_charts/summary_combined.png"): + """Combine pie and bar charts side by side if bar exists, else only pie.""" + from PIL import Image + + pie = Image.open(pie_path) + + if bar_path and Path(bar_path).exists(): + bar = Image.open(bar_path) + bar = bar.resize((bar.width * pie.height // bar.height, pie.height)) + combined = Image.new("RGB", (pie.width + bar.width, pie.height), (255, 255, 255)) + combined.paste(pie, (0, 0)) + combined.paste(bar, (pie.width, 0)) + else: + # Only pie chart + combined = pie.copy() + + combined.save(combined_path) + print(f"✅ Combined chart saved to {combined_path}") + + + +def _matplotlib_img(fig) -> str: + """Convert a matplotlib figure to base64 string.""" + buf = BytesIO() + plt.tight_layout() + fig.savefig(buf, format="png") + plt.close(fig) + buf.seek(0) + return base64.b64encode(buf.read()).decode("utf-8") + +def pytest_html_results_summary(prefix, summary, postfix, session): + """Inject donut pie + bar chart with reruns support (parallel-safe).""" + tr = session.config.pluginmanager.get_plugin("terminalreporter") + stats = tr.stats if tr and hasattr(tr, "stats") else {} + + passed = len(stats.get("passed", [])) + failed = len(stats.get("failed", [])) + skipped = len(stats.get("skipped", [])) + + # Reruns are recorded separately by pytest-rerunfailures + reruns = len(stats.get("rerun", [])) + + # --- Donut Pie Chart (Passed, Failed, Skipped) --- + pie_labels = ["Passed", "Failed", "Skipped"] + pie_sizes = [passed, failed, skipped] + pie_colors = ["#66bb6a", "#ef5350", "#fad000"] + + fig, ax = plt.subplots() + wedges, texts = ax.pie( + pie_sizes, + labels=None, + colors=pie_colors, + startangle=90, + wedgeprops=dict(width=0.4) + ) + ax.axis("equal") + + # Legend below the donut + plt.legend( + wedges, + [f"{l}: {v}" for l, v in zip(pie_labels, pie_sizes)], + title="Results", + loc="upper center", + bbox_to_anchor=(0.5, -0.08), + ncol=len(pie_labels) + ) + pie_img = _matplotlib_img(fig) + + # --- Bar Chart (Failures + Reruns) --- + bar_img = None + if failed > 0 or reruns > 0: + fig, ax = plt.subplots() + bars = ax.bar(["Failed", "Reruns"], [failed, reruns], color=["#ef5350", "#ff9933"]) + ax.set_title("Failures and Reruns") + ax.set_ylabel("Number of Tests") + plt.legend( + bars, + [f"Failed: {failed}", f"Reruns: {reruns}"], + loc="upper center", + bbox_to_anchor=(0.5, -0.12), + ncol=2 + ) + bar_img = _matplotlib_img(fig) + + # --- Embed in HTML report --- + html = "
" + html += f"

Test Summary

" + if bar_img: + html += f"

Failures and Reruns

" + html += "
" + + summary.append(html) diff --git a/Features/Powerbi_integration_exports/testCases/test_01_power_bi_tests.py b/Features/Powerbi_integration_exports/testCases/test_01_power_bi_tests.py new file mode 100644 index 000000000..34e475985 --- /dev/null +++ b/Features/Powerbi_integration_exports/testCases/test_01_power_bi_tests.py @@ -0,0 +1,198 @@ +import pytest + +from Features.Powerbi_integration_exports.userInputs.user_inputs import UserData +from HQSmokeTests.testPages.data.export_data_page import ExportDataPage +from HQSmokeTests.testPages.home.home_page import HomePage +from Features.Powerbi_integration_exports.testPages.data.power_bi_page import PowerBiPage + +""""Contains test cases related to the Data module""" + +values = dict() + +@pytest.mark.powerbi +def test_case_01_verify_odata_feed_ui(driver,settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + +@pytest.mark.powerbi +def test_case_02_verify_odata_feed_select_type(driver,settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_feed_type() + +@pytest.mark.powerbi +def test_case_03_verify_odata_feed_select_form(driver,settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_form() + +@pytest.mark.powerbi +def test_case_04_verify_odata_feed_select_application(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_form() + data.application_dropdown(UserData.reassign_cases_application) + +@pytest.mark.powerbi +def test_case_05_verify_odata_feed_select_menu(driver, settings): + home = (HomePage(driver, settings)) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_form() + data.application_dropdown(UserData.reassign_cases_application) + data.menu_dropdown() + +@pytest.mark.powerbi +def test_case_06_verify_odata_feed_cancel_selection(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.cancel_feed() + +def test_case_07_verify_save_and_delete_odata_feed_form(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_form() + data.form_feed(UserData.reassign_cases_application,UserData.reassign_menu,UserData.reassign_form) + data.adding_odata_feed() + data.save_odata_feed() + data.delete_feed() + +@pytest.mark.powerbi +def test_case_08_verify_save_odata_feed_form(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_form() + data.form_feed(UserData.reassign_cases_application,UserData.reassign_menu,UserData.reassign_form) + data.adding_odata_feed() + +@pytest.mark.powerbi +def test_case_09_validate_odata_feed_show_advance_question(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_form() + data.form_feed(UserData.reassign_cases_application, UserData.reassign_menu, UserData.reassign_form) + data.adding_odata_feed() + data.show_advance_question() + +@pytest.mark.powerbi +def test_case_10_verify_save_and_delete_odata_feed_case(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_case() + data.create_case_feed(UserData.case) + data.adding_odata_feed() + data.save_odata_feed() + data.delete_feed() + + +@pytest.mark.powerbi +def test_case_11_verify_save_and_copy_odata_feed(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_form() + data.form_feed(UserData.reassign_cases_application,UserData.reassign_menu,UserData.reassign_form) + data.adding_odata_feed() + data.save_odata_feed() + data.copy_edit_feed() + +@pytest.mark.powerbi +def test_case_12_verify_add_description_to_odata_feed(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_form() + data.form_feed(UserData.reassign_cases_application,UserData.reassign_menu,UserData.reassign_form) + data.adding_odata_feed() + data.add_description() + +@pytest.mark.powerbi +def test_case_13_verify_bulk_create_bulk_delete_odata_feed(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.create_multiple_odata_feed(10) + data.validate_go_to_page() + data.power_bi_tableau_integration_bulk_delete() + +@pytest.mark.powerbi +def test_case_14_verify_delete_questions_on_odata_feed_page(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_form() + data.form_feed(UserData.reassign_cases_application,UserData.reassign_menu,UserData.reassign_form) + data.adding_odata_feed() + data.delete_questions() + +@pytest.mark.powerbi +def test_case_15_verify_deidentified_odata_feed(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_form() + data.form_feed(UserData.reassign_cases_application,UserData.reassign_menu,UserData.reassign_form) + data.adding_odata_feed() + data.validate_de_identified() + +@pytest.mark.powerbi +def test_case_16_verify_view_created_odata_feed(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + username = settings["login_username"] + password = settings["login_password"] + data.power_bi_page_ui('y') + data.select_form() + data.form_feed(UserData.reassign_cases_application,UserData.reassign_menu,UserData.reassign_form) + data.adding_odata_feed() + data.add_description() + data.view_odata_feed(username, password) + +@pytest.mark.powerbi +def test_case_17_verify_odata_feed_edit_filters(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_form() + data.form_feed(UserData.Basic_tests_application,UserData.Basic_menu,UserData.Repeat_form) + data.adding_odata_feed() + data.verify_repeat_checkbox() + data.save_odata_feed() + data.edit_filters() + +@pytest.mark.powerbi +def test_case_18_verify_odata_feed_include_parent(driver, settings): + home = HomePage(driver, settings) + data = PowerBiPage(driver) + home.data_menu() + data.power_bi_page_ui('y') + data.select_case() + data.create_case_feed(UserData.parent) + data.adding_odata_feed() + data.verify_parent_checkbox() + data.save_odata_feed() \ No newline at end of file diff --git a/Features/Powerbi_integration_exports/testPages/__init__.py b/Features/Powerbi_integration_exports/testPages/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Features/Powerbi_integration_exports/testPages/data/__init__.py b/Features/Powerbi_integration_exports/testPages/data/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Features/Powerbi_integration_exports/testPages/data/power_bi_page.py b/Features/Powerbi_integration_exports/testPages/data/power_bi_page.py new file mode 100644 index 000000000..5325cd810 --- /dev/null +++ b/Features/Powerbi_integration_exports/testPages/data/power_bi_page.py @@ -0,0 +1,275 @@ +import os.path +import time +from time import sleep + +from selenium.common import TimeoutException, ElementClickInterceptedException +from selenium.webdriver import Keys +from selenium.webdriver.common.by import By +from selenium.webdriver.support.select import Select + +from common_utilities.selenium.base_page import BasePage +from common_utilities.generate_random_string import fetch_random_string +from common_utilities.path_settings import PathSettings +from Features.Powerbi_integration_exports.userInputs.user_inputs import UserData + + +""""Contains test page elements and functions related to the Lookup Table module""" + +class PowerBiPage(BasePage): + + def __init__(self, driver): + super().__init__(driver) + self.power_bi = (By.XPATH, "//*[@id='hq-sidebar']/nav/ul[1]/li[7]/a") + self.add_odata_feed = (By.XPATH, "//*[@id='create-export']/p/a") + self.disabled = (By.XPATH,"//*[@data-bind='visible: showSubmit, disable: disableSubmit']") + self.feed_type = (By.XPATH,"//select[@id='id_model_type']") + self.feed_type_visible = (By.XPATH,"//*[@data-bind='visible: modelType()']") + self.case_type = (By.XPATH,"//*[@for='id_case_type']") + self.cancel_button = (By.XPATH,"//*[@id='createExportOptionsModal']/div/form/div/div[7]/button[1]") + self.applications = (By.XPATH,"//select[contains(@name,'application')]") + self.app_dropdown = (By.XPATH,"//input[contains(@aria-controls,'id_application-results')]") + self.users_list_item = "//ul[@role='listbox']/li[contains(.,'{}')]" + self.menu =(By.XPATH,"//select[contains(@name,'module')]") + self.menu_select = (By.XPATH,"//input[@aria-controls='select2-id_module-results']") + self.form = (By.XPATH,"//select[contains(@placeholder,'Select Form')]") + self.add_export_conf = (By.XPATH, "//button[@data-bind='visible: showSubmit, disable: disableSubmit']") + self.submission_msg = (By.XPATH, "//span[contains(@data-bind,'text: submissionCountText')]") + self.case = (By.XPATH,"//span[@aria-controls='select2-id_case_type-container']") + self.case_select = (By.XPATH,"//input[@aria-controls='select2-id_case_type-results']") + self.save = (By.XPATH,"//button[@type='submit'][contains(@data-bind,'click: save')]") + self.settings = (By.XPATH, "//*[@id='customize-export']/header/div/div/h3") + self.success = (By.XPATH,"//*[@class='alert alert-margin-top fade in alert-success']") + self.odata_feed_name = (By.XPATH,"//input[@id='export-name']") + self.powerBI_tab_int = (By.LINK_TEXT, "PowerBi/Tableau Integration") + self.select_all_btn = (By.XPATH, '//button[@data-bind="click: selectAll"]') + self.delete_selected_exports = (By.XPATH, '//a[@href= "#bulk-delete-export-modal"]') + self.bulk_delete_confirmation_btn = (By.XPATH, '//button[@data-bind="click: BulkExportDelete"]') + self.show_advance = (By.XPATH, "//span[@data-bind='visible: !table.showAdvanced()']") + self.hide_advance = (By.XPATH,"//span[@data-bind='visible: table.showAdvanced']") + self.show_delete = (By.XPATH,"//span[@data-bind='visible: !table.showDeleted()']") + self.copy_edit_button = (By.XPATH,"(//*[@data-bind='if: isOData()'])[1]") + self.description = (By.XPATH,("//*[@id='export-description']")) + self.disabled_button =(By.XPATH,("//td[5][./input[@disabled]]//preceding-sibling::td//input[@type='checkbox' and @disabled='disabled']")) + self.copy_odata_feed = (By.XPATH,("//*[@id='export-list']/div[2]/div[1]/div[2]/table/tbody/tr/td[2]/div/span/a")) + self.link = (By.XPATH,("//*[@id='export-list']/div[2]/div[1]/div[2]/table/tbody/tr/td[2]/div/input")) + self.delete = (By.XPATH,("(//a[@data-bs-toggle='modal'])[9]")) + self.delete_button = (By.XPATH,("//*[@id='export-list']/div[2]/div[1]/div[2]/table/tbody/tr/td[5]/div/a")) + self.edit_filter = (By.XPATH,("//*[@id='export-list']/div[2]/div[1]/div[2]/table/tbody/tr/td[3]/a[2]")) + self.daterange =(By.XPATH,("//*[@id='id_date_range']")) + self.save_filter = (By.XPATH,("//*[@id='setFeedFiltersModal']/div/form/div/div[3]/button[2]")) + self.pagination = (By.XPATH,("//*[@id='export-list']/div[2]/div[1]/div[2]/pagination/div/div/div[2]/nav/ul/li[3]/a")) + self.delete_question = (By.XPATH,("//span[@data-bind='visible: !table.showDeleted()']")) + self.process = (By.XPATH, "//*[@id='export-process-deleted-applications']/div/div/div[3]/button[2]") + self.refresh_page = (By.XPATH, "//*[@id='export-process-deleted-applications']/div/div/div[3]/button[2]") + self.hide_delete_question = (By.XPATH,"//*[@data-bind='visible: table.showDeleted()']") + self.allow_sensitive = (By.XPATH, "//*[@id='customize-export']/form/fieldset[3]/button") + self.de_identified_text = (By.XPATH,"//*[@id='is_deidentified']") + self.label = (By.XPATH,"(//label[@data-bind='visible: isDeid()'][normalize-space()='De-Identified'])[1]") + self.check_data = (By.XPATH, "//*[contains(text(), '@odata.context')]") + self.copy_odata_link = (By.XPATH,"//*[@id='export-list']/div[2]/div[1]/div[2]/table/tbody/tr/td[2]/div[2]/button" ) + self.copy_odata_link_form = (By.XPATH,"(//*[contains(@class,'form-control input-sm')])[1]") + self.copy_odata_link_btn_form = ( + By.XPATH, + "//div[contains(@data-bind,'text-body-secondary')][.//span[text()='"+ UserData.name +"']]//following-sibling::div[contains(@data-bind,'showLink')]//button") + self.repeat_checkbox = (By.XPATH,"//*[@id='customize-export']/form/fieldset[2]/div[2]/legend/span[1]/input") + self.parent_checkbox = (By.XPATH,"//*[@id='customize-export']/form/fieldset[2]/div[3]/legend/span[1]/input") + self.case_id_duplicate =(By.XPATH,"//body[1]/div[1]/div[3]/div[1]/div[2]/div[2]/form[1]/fieldset[2]/div[1]/div[1]/table[1]/tbody[1]/tr[9]/td[1]/input[1]") + self.repeat_checkbox_new =(By.XPATH,"//*[@id='customize-export']/form/fieldset[2]/div[4]/legend/span[1]/input") + + def power_bi_page_ui(self,flag): + self.wait_to_click(self.power_bi,2) + #initially to delete the existing files. + if flag == 'Y': + self.power_bi_tableau_integration_bulk_delete() + self.wait_to_click(self.add_odata_feed) + + def select_feed_type(self): + self.is_present_and_displayed(self.disabled,10) + print("Add OData Feed button is disabled") + + def select_form(self): + self.wait_to_click(self.feed_type,50) + ss = Select(self.find_element(self.feed_type)) + ss.select_by_visible_text('Form') + self.is_present_and_displayed(self.feed_type_visible) + print("App type, application,Menu , form are displayed") + + def application_dropdown(self,select_app): + self.wait_to_click(self.applications) + app_dropdown=[] + app_dropdown=(self.find_elements_texts(self.applications)) + time.sleep(10) + print(app_dropdown) + self.wait_to_click(self.applications) + self.select_by_text(self.applications,select_app) + + def menu_dropdown(self): + self.wait_to_click(self.menu) + menu_dropdown=[] + menu_dropdown=(self.find_elements_texts(self.menu_select)) + time.sleep(10) + print(menu_dropdown) + + def select_case(self): + self.wait_to_click(self.feed_type,30) + ss = Select(self.find_element(self.feed_type)) + # Select option by visible text + ss.select_by_visible_text('Case') + self.is_present_and_displayed(self.case_type,15) + print("case type field displayed") + + def cancel_feed(self): + time.sleep(10) + self.wait_to_click(self.cancel_button,10) + + def form_feed(self,app_select,menu_select,form_select): + self.wait_to_click(self.applications) + self.select_by_text(self.applications,app_select) + self.wait_to_click(self.menu) + self.select_by_text(self.menu,menu_select) + self.wait_to_click(self.form) + self.select_by_text(self.form,form_select) + self.is_present_and_displayed(self.submission_msg) + print("Form submission message displayed") + + def adding_odata_feed(self): + self.wait_to_click(self.add_export_conf) + self.is_present_and_displayed(self.settings,10) + print ("odata feed settings page displayed") + self.wait_to_click(self.odata_feed_name) + self.wait_to_clear_and_send_keys(self.odata_feed_name,UserData.name+Keys.TAB) + self.wait_to_click(self.case_id_duplicate) + self.scroll_to_bottom() + self.is_present_and_displayed(self.save,10) + + def verify_repeat_checkbox(self): + self.scroll_to_element(self.repeat_checkbox) + self.wait_to_click(self.repeat_checkbox) + self.scroll_to_element(self.repeat_checkbox_new) + self.wait_to_click(self.repeat_checkbox_new) + + def verify_parent_checkbox(self): + self.scroll_to_element(self.parent_checkbox) + self.js_click(self.parent_checkbox,2) + + + def delete_bulk_exports(self): + try: + self.wait_to_click(self.select_all_btn) + self.wait_to_click(self.delete_selected_exports) + self.wait_to_click(self.bulk_delete_confirmation_btn) + time.sleep(10) + except TimeoutException: + print("No exports available") + + + def power_bi_tableau_integration_bulk_delete(self): + try: + self.wait_and_sleep_to_click(self.powerBI_tab_int) + except ElementClickInterceptedException: + self.js_click(self.powerBI_tab_int) + self.delete_bulk_exports() + + def create_case_feed(self,select_case): + self.wait_to_click(self.case,10) + self.wait_for_element(self.case_select) + self.send_keys(self.case_select, select_case) + self.wait_to_click((By.XPATH, self.users_list_item.format(select_case))) + + def save_odata_feed(self): + self.js_click(self.save) + time.sleep(2) + self.is_present_and_displayed(self.success,10) + print("odata feed is created") + + def show_advance_question(self): + self.wait_to_click(self.show_advance) + self.wait_to_click(self.hide_advance) + self.is_present_and_displayed(self.show_advance) + self.is_present_and_displayed(self.show_delete) + print("show advance and show delete questions buttons are displayed") + + def copy_edit_feed(self): + self.wait_to_click(self.copy_edit_button,20) + self.wait_to_click(self.odata_feed_name) + self.wait_to_clear_and_send_keys(self.odata_feed_name,UserData.updatedname) + self.wait_to_click(self.save) + + def add_description(self): + self.wait_to_click(self.description,5) + self.send_keys(self.description, UserData.description+Keys.TAB) + assert self.is_present_and_displayed(self.disabled_button,10) + print("disabled button present") + self.scroll_to_bottom() + time.sleep(2) + self.js_click(self.save) + print("clicked on saved button") + self.is_present_and_displayed(self.success, 10) + + + def delete_feed(self): + self.wait_to_click(self.delete) + self.wait_to_click(self.delete_button) + + def edit_filters(self): + self.wait_to_click(self.edit_filter,2) + self.wait_to_click(self.daterange, 5) + ss = Select(self.find_element(self.daterange)) + # Select option by visible text + ss.select_by_visible_text('Last year') + self.wait_to_click(self.save_filter,10) + self.wait_to_click(self.edit_filter) + print("Data range values", self.daterange) + + + def create_multiple_odata_feed(self,no_of_feeds): + for i in range(1,no_of_feeds): + self.power_bi_page_ui(10) + self.select_form() + self.form_feed(UserData.reassign_cases_application,UserData.reassign_menu,UserData.reassign_form) + self.adding_odata_feed() + self.save_odata_feed() + print("create multiple odata feeds completed") + + + def validate_go_to_page(self): + self.wait_to_click(self.pagination,10) + print("pagination working fine") + + def delete_questions(self): + self.wait_to_click(self.delete_question) + self.is_present_and_displayed(self.hide_delete_question) + print("Deleted questions become included in the question list for form feeds") + + def validate_de_identified(self): + self.scroll_to_bottom() + self.js_click(self.allow_sensitive,2) + self.js_click(self.de_identified_text,2) + self.js_click(self.save,10) + self.is_present_and_displayed(self.label) + print("odatafeed marked as de-identified") + + def view_odata_feed(self,username,password): + self.driver.refresh() + time.sleep(10) + print(self.copy_odata_link_btn_form) + self.wait_and_sleep_to_click(self.copy_odata_link_btn_form,40) + self.get_url_paste_browser(username, password, 'forms') + self.assert_odata_feed_data() + + def get_url_paste_browser(self, username, password, item): + global odata_feed_link1 + if item == 'forms': + odata_feed_link1 = self.wait_to_get_value(self.copy_odata_link_form) + print("===="+odata_feed_link1) + final_URL_case = f"https://{username}:{password}@{odata_feed_link1[8:]}" + print("--------"+final_URL_case) + time.sleep(10) + self.driver.get(final_URL_case) + + def assert_odata_feed_data(self): + odata_feed_data = self.driver.page_source + verify_data = self.find_elements(self.check_data) + assert len(verify_data) > 0, "Odata feed is Empty " + # self.driver.close() # Close the feed URL + self.driver.back() \ No newline at end of file diff --git a/Features/Powerbi_integration_exports/userInputs/__init__.py b/Features/Powerbi_integration_exports/userInputs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/Features/Powerbi_integration_exports/userInputs/user_inputs.py b/Features/Powerbi_integration_exports/userInputs/user_inputs.py new file mode 100644 index 000000000..e73c879a8 --- /dev/null +++ b/Features/Powerbi_integration_exports/userInputs/user_inputs.py @@ -0,0 +1,29 @@ +""""Contains test data that are used as user inputs across various areasn in CCHQ""" +import os +import random +import string + +from common_utilities.generate_random_string import fetch_random_string +from common_utilities.path_settings import PathSettings + + +class UserData: + + + """User Test Data""" + user_input_base_dir = os.path.dirname(os.path.abspath(__file__)) + # Pre-setup application and case names + field_val = 'table'+ str(fetch_random_string()) + reassign_cases_application = 'Reassign Cases' + reassign_menu = 'Case List' + reassign_form = 'Registration Form' + reassign_case = 'reassign' + name = 'powerbi_odatafeed1' + updatedname = 'updated_powerbi_odatafeed1' + description = 'testing the powerbi form report' + Basic_tests_application = 'Basic Tests' + Basic_menu = 'Formplayer Specific Tests' + Repeat_form = '[Formplayer] Repeats' + parent = 'sub_case_one' + odata_feed_form = '' + case='case' \ No newline at end of file diff --git a/HQSmokeTests/testPages/users/roles_permissions_page.py b/HQSmokeTests/testPages/users/roles_permissions_page.py index 37de06820..432fd06c1 100644 --- a/HQSmokeTests/testPages/users/roles_permissions_page.py +++ b/HQSmokeTests/testPages/users/roles_permissions_page.py @@ -48,6 +48,9 @@ def __init__(self, driver, settings): self.confirm_role_delete = (By.XPATH, "//div[@class='btn btn-danger']") self.full_org_access_checkbox = (By.XPATH, "//label[contains(.,'Full Organization Access')]//following-sibling::div//input") self.access_all_reports_checkbox = (By.XPATH, "//input[@id='access-all-reports-checkbox']") + self.edit_data = (By.XPATH, "//div[@id='user-roles-table']/div[@class='panel-body']/div[@class='modal fade in']/div[@class='modal-dialog']/form/div[@class='modal-content']/div[@class='modal-body']/div[@class='form form-horizontal']/fieldset/div[3]/div[@class='form-group'][7]/div[@class='col-sm-2 controls'][1]/div[@class='form-check']/label") + self.view_data_dictionary = (By.XPATH, "//input[@id='view-data-dict-checkbox']") + self.edit_data_dictionary = (By.XPATH, "//input[@id='edit-data-dict-checkbox']") self.web_user_permission = "//th[./span[.='{}']]//following-sibling::td/div[contains(@data-bind,'edit_web_users')]/i[contains(@class,'check')]" self.mobile_worker_permission = "//th[./span[.='{}']]//following-sibling::td/div[contains(@data-bind,'edit_commcare_users')]/i[contains(@class,'check')]" @@ -124,9 +127,7 @@ def delete_test_roles(self): def add_non_admin_role(self): self.wait_to_click(self.add_new_role) - self.wait_for_element(self.role_name) - self.send_keys(self.role_name, self.role_non_admin_created) - + self.wait_to_clear_and_send_keys(self.role_name, self.role_non_admin_created) self.wait_to_click(self.edit_mobile_worker_checkbox) self.scroll_to_element(self.access_all_reports_checkbox) is_checked = self.get_attribute(self.access_all_reports_checkbox, 'checked') @@ -151,38 +152,25 @@ def add_non_admin_role(self): self.wait_to_click(self.save_button) assert self.is_present_and_displayed(self.role_non_admin), "Role not added successfully!" - print("Role added successfully") return self.role_non_admin_created - - def add_shared_export_role(self, name, flag='NO'): + def add_non_admin_role_dd(self, value): self.wait_to_click(self.add_new_role) - self.wait_to_clear_and_send_keys(self.role_name, name) - time.sleep(1) - self.click(self.edit_mobile_worker_checkbox) - time.sleep(0.5) - self.click(self.edit_web_user_checkbox) - time.sleep(0.5) - self.click(self.data_checkbox) - time.sleep(0.5) - self.scroll_to_element(self.manage_shared_exports) - if flag == 'YES': - time.sleep(2) - self.click(self.manage_shared_exports) - time.sleep(2) - time.sleep(0.5) - self.scroll_to_element(self.access_all_reports_checkbox) + self.wait_to_clear_and_send_keys(self.role_name, self.role_non_admin_created) time.sleep(1) - self.click(self.access_all_reports_checkbox) - time.sleep(0.5) + self.js_click(self.edit_data,5) + self.js_click(self.view_data_dictionary) + if value == 1: + print("only view access selected") + elif value ==2: + time.sleep(5) + self.js_click(self.edit_data_dictionary) + else: + self.js_click(self.view_data_dictionary) + self.js_click(self.edit_data_dictionary) + self.js_click(self.edit_data_dictionary) self.scroll_to_element(self.save_button) time.sleep(0.5) - self.click(self.save_button) + self.js_click(self.save_button) time.sleep(2) - assert self.is_present_and_displayed((By.XPATH, self.role_no_shared_export.format(name))), "Role not added successfully!" - assert self.is_present_and_displayed((By.XPATH, self.web_user_permission.format(name))), "Web User Permission not present" - assert self.is_present_and_displayed((By.XPATH, self.mobile_worker_permission.format(name))), "Mobile Worker Permission not present" - if flag == "NO": - assert not self.is_present_and_displayed((By.XPATH, self.managed_shared_export_permission.format(name)), 5), "Shared Export Permission is present" - else: - assert self.is_present_and_displayed((By.XPATH, self.managed_shared_export_permission.format(name))), "Shared Export Permission not present" + return self.role_non_admin_created diff --git a/HQSmokeTests/testPages/users/web_user_page.py b/HQSmokeTests/testPages/users/web_user_page.py index 321b3a837..a1b93216b 100644 --- a/HQSmokeTests/testPages/users/web_user_page.py +++ b/HQSmokeTests/testPages/users/web_user_page.py @@ -192,28 +192,25 @@ def upload_web_users(self): assert self.is_present_and_displayed(self.import_complete, 150), "Upload Not Completed! Taking Longer to process.." print("File uploaded successfully") - def edit_user_permission(self, rolename): + def edit_user_permission(self, role_name): self.wait_to_click(self.web_users_menu) self.wait_for_element(self.search_user) self.wait_to_clear_and_send_keys(self.search_user, UserData.p1p2_user) - + time.sleep(2) self.wait_to_click(self.search_user_btn) - - self.wait_for_element(self.user_link) - self.wait_to_click(self.user_link) + self.wait_for_element(self.user_link,25) + self.wait_to_click(self.user_link,20) self.wait_for_element(self.select_project_role_id) - self.select_by_text(self.select_project_role_id, rolename) - self.wait_to_click(self.update_role_btn) - + self.select_by_text(self.select_project_role_id, role_name) + self.wait_to_click(self.update_role_btn,5) self.scroll_to_element(self.location_field) if self.is_present(self.remove_location): self.wait_to_click(self.remove_location) print("removed location") - self.wait_to_click(self.location_field) self.send_keys(self.location_field, "Test Location") self.wait_to_click(self.location_value) - self.wait_to_click(self.update_location_btn) + self.wait_to_click(self.update_location_btn,5) def change_user_role(self, username, role):