From abdcce604218995c959fb2cec942aae00334d62b Mon Sep 17 00:00:00 2001 From: Roxie Gibson Date: Mon, 25 Jan 2021 20:48:43 +0000 Subject: [PATCH] refactor: file structure change to multiple files --- civicrm_tester/__init__.py | 0 civicrm_tester/activities_tab.py | 67 +++ civicrm_tester/base.py | 184 ++++++++ civicrm_tester/contact_export.py | 103 +++++ .../steeringcommittee_print_labels.py | 81 ++++ main.py | 430 +----------------- 6 files changed, 440 insertions(+), 425 deletions(-) create mode 100644 civicrm_tester/__init__.py create mode 100644 civicrm_tester/activities_tab.py create mode 100644 civicrm_tester/base.py create mode 100644 civicrm_tester/contact_export.py create mode 100644 civicrm_tester/steeringcommittee_print_labels.py diff --git a/civicrm_tester/__init__.py b/civicrm_tester/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/civicrm_tester/activities_tab.py b/civicrm_tester/activities_tab.py new file mode 100644 index 0000000..5d06776 --- /dev/null +++ b/civicrm_tester/activities_tab.py @@ -0,0 +1,67 @@ +from selenium.webdriver.common.by import By +from selenium.webdriver.support.select import Select + +from .base import BaseTester + + +class ActivitiesTab(BaseTester): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.title("Activities Tab") + self.desc("Testing if a contacts activities tab displays all activies") + self.contact_page = self.base_url + "/civicrm/contact/view/?reset=1&cid={}" + + def _test(self, cid: str): + self.debug("loading contact page for CID %s" % cid) + self.browser.get(self.contact_page.format(cid)) + self.wait_until_visible((By.ID, "ui-id-10")) + # Contact page as loaded + activities_tab_button = self.find_element_by_id("ui-id-10") + num_element = activities_tab_button.find_element_by_tag_name("em") + num_of_activ = int(num_element.text) + activities_tab_button.click() + table_row_selector = ( + By.CSS_SELECTOR, "#DataTables_Table_0 > tbody tr" + ) + self.debug( + "clicked activities button and waiting for activities page to load" + ) + self.wait_until_visible(table_row_selector) + # Activities page as now loaded + table_length_dropdown = Select( + self.browser.find_element_by_xpath( + "//select[@name='DataTables_Table_0_length']" + ) + ) + table_length_dropdown.select_by_visible_text("100") + self.wait_until_clickable(table_row_selector) + num_of_rows = len( + self.browser.find_elements_by_css_selector("tr.crm-entity") + ) + if num_of_activ == num_of_rows: + self.passed( + "expected number of activities found in activity table for CID %s. Expected: %d, Actual %d" + % (cid, num_of_activ, num_of_rows) + ) + elif num_of_activ > 100 and num_of_rows == 100: + self.issue( + "Number of activities for CID %s is above 100. This is the max amount the table can display. The table is displaying 100 entries. Pagination is not supported by the script." + % cid + ) + else: + self.failed( + "Number of activities is lower than expected for CID %s. Most likely didn't load properly Expected: %d, Actual %d" + % (cid, num_of_activ, num_of_rows) + ) + + def test_all_hardcoded_contacts(self): + """Loops self.test with all hardcoded contact ID's + Using MP's as MP's are public knowledge and often have activities within the crm system + names provided in comments here for debugging purposes + """ + cid_da = "42219" # Debbie Abrahams + cid_kh = "82163" # Kate Hollern + cid_na = "42269" # Nigel Addams + #cid_db = "43193" Use to test 100 max limit + cid_tuple = (cid_da, cid_kh, cid_na) + self._test_all(cid_tuple) diff --git a/civicrm_tester/base.py b/civicrm_tester/base.py new file mode 100644 index 0000000..694ed22 --- /dev/null +++ b/civicrm_tester/base.py @@ -0,0 +1,184 @@ +import logging +import re +import urllib.parse as urlparse +from urllib.parse import parse_qs + +import requests +from rich.console import Console +from rich.logging import RichHandler +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + + +class BaseTester: + """ + Base class for all test suites + + One caveat: This class defines a pseudo function "_test" which is called by "_test_all" + Overloading classes are to define their own tests to be run by selenium after logging in + Then they are to call "_test_all" which will run these tests in a loop with given variables + This allows the test to be run multiple times with different terms to make sure the test covers all bases. + """ + def __init__(self, user: str, passwd: str, dev: bool, show_browser: bool): + self.console = Console() + # Disable pdfminers logger cause its annoying as fuck and provides no value + logging.getLogger("pdfminer").setLevel(100) + logging.basicConfig( + level="INFO", + format="%(message)s", + datefmt="[%X]", + handlers=[RichHandler()] + ) + self.log = logging.getLogger("civiCRM-tester") + self.user = user + self.passwd = passwd + if dev: + self.base_url = "https://crm-dev.caat.org.uk" + else: + self.base_url = "https://crm.caat.org.uk" + + firefox_options = webdriver.FirefoxOptions() + firefox_options.headless = not show_browser + self.browser = webdriver.Firefox(options=firefox_options) + self.wait = WebDriverWait(self.browser, 20) + + def debug(self, msg: str): + """Wrapper for logging debug levels for rich output""" + return self.log.debug(msg, extra={"markup": True}) + + def info(self, msg: str): + """Wrapper for logging info levels for rich output""" + return self.log.info(msg, extra={"markup": True}) + + def warn(self, msg: str): + """Wrapper for logging warn levels for rich output""" + return self.log.warning(msg, extra={"markup": True}) + + def error(self, msg: str): + """Wrapper for logging error levels for rich output""" + return self.log.error(msg, extra={"markup": True}) + + def critical(self, msg: str): + """Wrapper for logging critical levels for rich output""" + return self.log.critical(msg, extra={"markup": True}) + + def passed(self, msg: str): + """Wrapper for logging passed tests""" + return self.info("[reverse green]PASSED[/reverse green] %s" % msg) + + def issue(self, msg: str): + """ + Wrapper for logging tests with issues + This is used for tests where the result is inconclusive due to some issue that is neither a fail nor pass. + Usually requires human interaction + """ + return self.warn("[reverse yellow]ISSUE[/reverse yellow] %s" % msg) + + def failed(self, msg: str): + """Wrapper for logging failed tests""" + return self.info("[reverse red]FAILED[/reverse red] %s" % msg) + + def title(self, test_name: str): + """Create title header for new suite of tests""" + return self.info("[cyan]%s[/cyan]" % test_name) + + def desc(self, test_desc: str): + """Log test description of upcoming number of tests""" + return self.info("%s" % test_desc) + + def login(self): + """ Login to civicrm so we can continue with the proper cookies """ + self.browser.get(self.base_url) + self.debug("Logging in as %s @ %s" % (self.user, self.base_url)) + username = self.browser.find_element_by_id("edit-name") + password = self.browser.find_element_by_id("edit-pass") + submit = self.browser.find_element_by_id("edit-submit") + username.send_keys(self.user) + password.send_keys(self.passwd) + submit.click() + + # Wait for the js elements load so we know the cookies are good. + # Waits for "Recent Items" part of sidebar which is unique when logged in + self.wait.until( + EC.visibility_of_element_located((By.ID, "block-civicrm-2")) + ) + self.debug("Successfully logged in as %s" % self.user) + + def logout(self): + """Log browser out of civicrm to reset our test environment""" + self.browser.get(self.base_url + "/user/logout") + # Wait for the next page to load to finish logging out + self.wait.until( + EC.visibility_of_element_located((By.ID, "tabs-wrapper")) + ) + + def _test(self, *args): + """Placeholder to be overwritten by overloading classes""" + + def _test_all(self, test_strings: tuple[str]): + """Loops testing over the given terms""" + try: + self.login() + for term in test_strings: + self._test(term) + finally: + self.logout() + self.browser.close() + + def find_element_by_id(self, *args, **kwargs): + """Alias for browser.find_element_by_id""" + return self.browser.find_element_by_id(*args, **kwargs) + + def find_element_by_css_selector(self, *args, **kwargs): + """Alias for browser.find_element_by_css_selector""" + return self.browser.find_element_by_css_selector(*args, **kwargs) + + def wait_until_visible(self, locator): + """Alias for using inbuilt wait object for wait.until(EC.visibility_of_element_located)""" + return self.wait.until(EC.visibility_of_element_located(locator)) + + def wait_until_clickable(self, locator): + """Alias for using inbuilt wait object for wait.until(EC.element_to_be_clickable)""" + return self.wait.until(EC.element_to_be_clickable(locator)) + + +class SearchExportTester(BaseTester): + def _get_export_id(self) -> str: + """Parses url to get the param used to ID what search we are currently doing""" + export_page_url = self.browser.current_url + parsed = urlparse.urlparse(export_page_url) + qf_key = parse_qs(parsed.query)['qfKey'] + self.debug("got qf_key '%s' from url" % qf_key) + return qf_key + + def download(self, data: dict) -> requests.Response: + """ + Download exports from searches manually using requests + This is because managing downloads within Selenium is a nightmare + Function tries to replicate the download within the browser as much as possible + """ + session_cookie = {} + for cookie in self.browser.get_cookies(): + if re.findall(r"^SSESS.*", cookie.get("name")): + session_cookie = cookie + self.debug("session cookie: %s" % session_cookie) + if not session_cookie: + self.critical("NO SESSION COOKIE FOUND. Are you logged in?") + raise RuntimeError("No session cookie found.") + + session = requests.Session() + session.cookies.update( + {session_cookie["name"]: session_cookie["value"]} + ) + # Most of this data is replicating the export settings so that the response is the csv data + data = { + "qfKey": self._get_export_id(), + "entryURL": self.base_url + "/civicrm/contact/search", + **data # Add provided export data required for specific export requests + } + res = session.request( + "POST", self.base_url + "/civicrm/contact/search", data=data + ) + return res diff --git a/civicrm_tester/contact_export.py b/civicrm_tester/contact_export.py new file mode 100644 index 0000000..a7343f6 --- /dev/null +++ b/civicrm_tester/contact_export.py @@ -0,0 +1,103 @@ +import csv +import io +import re + +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys + +from .base import SearchExportTester + + +class ContactExport(SearchExportTester): + """Tests if exporting contacts from a search returns a CSV file with all contacts.""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.title("Contact Export") + self.desc( + "Testing if exporting contacts from a search returns a CSV file with all expected contacts." + ) + self.search_url = self.base_url + "/civicrm/contact/search" + + self.contact_selectall_id = "CIVICRM_QFID_ts_all_4" + self.contact_dropdown_id = "select2-chosen-4" + self.contact_exportoption_id = "select2-result-label-15" + + def download_csv(self): + # Data of the request that is specific for this export and not genertic like qr_key + data = { + "_qf_Select_next": "Continue", + "exportOption": 1, + "mergeOption": 0, + "postal_greeting": 1, + "addressee": 1, + } + return self.download(data) + + def calculate_exported_contacts_number(self) -> int: + """ + Downloads csv, opens it in a string buffer using StringIO and passes it to the csv lib + Counts number of rows (excl. header) and returns that value + """ + res = self.download_csv() + csv_file = io.StringIO(res.text) + + # Dict reader should remove headers + exported_csv = csv.DictReader(csv_file) + exported_number_exports = sum(1 for row in exported_csv) + self.debug( + "found %d rows in exported csv excluding header row" % + exported_number_exports + ) + + return exported_number_exports + + def _test(self, search_term: str): + """ + Test Description: + Go to the contact search url + Search for the search term + Select all contacts and set them to export + Get the login cookie and search ID and download the export manually with requests + Save the file in tmp + Read it to check the number of exported contacts is the same as reported in the UI + """ + self.browser.get(self.search_url) + search_box = self.find_element_by_id("sort_name") + search_box.send_keys(search_term) + search_box.send_keys(Keys.ENTER) + self.debug("searching for contacts with term '%s'" % search_term) + self.wait_until_visible( + (By.ID, "alpha-filter") + ) #wait for table to load + self.debug("table of results has loaded") + + results_text = self.find_element_by_css_selector( + ".form-layout-compressed > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(2) > label:nth-child(2)" + ).text + matches = re.findall( + r"(\d+)", results_text + ) # Should just be one match in normal cases + result_no = int(matches[0]) + + self.debug("exporting results using the magic dropdown") + self.find_element_by_id(self.contact_selectall_id).click() + self.find_element_by_id(self.contact_dropdown_id).click() + self.find_element_by_id(self.contact_exportoption_id).click() + self.wait_until_visible((By.CSS_SELECTOR, ".crm-block")) + + exported_number_exports = self.calculate_exported_contacts_number() + if exported_number_exports == (result_no): + self.passed( + "Number of expected contact exports for '%s' matches actual number of exports - Expected: %d, Actual: %d" + % (search_term, result_no, exported_number_exports) + ) + else: + self.failed( + "Number of expected contact exports for '%s' WAS NOT EQUAL to actual number of exports - Expected: %d, Actual: %d" + % (search_term, result_no, exported_number_exports) + ) + + def test_hardcoded_search_terms(self): + """Loops over the test with three hardcoded search terms""" + search_terms = ("John", "e", "Smith") + self._test_all(search_terms) diff --git a/civicrm_tester/steeringcommittee_print_labels.py b/civicrm_tester/steeringcommittee_print_labels.py new file mode 100644 index 0000000..e43f946 --- /dev/null +++ b/civicrm_tester/steeringcommittee_print_labels.py @@ -0,0 +1,81 @@ +import io +import re + +from pdfminer.high_level import extract_text +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys + +from .base import SearchExportTester + + +class SteeringCommitteePrintLabels(SearchExportTester): + """Tests the pdf labels for the SteeringCommitee show all contacts names""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.title("Steering Committee Print Labels") + self.desc( + "Testing the pdf labels for the SteeringCommittee show all contacts names" + ) + self.search_url = self.base_url + "/civicrm/contact/search" + self.group_dropdown = "s2id_autogen2" + self.search_button = "_qf_Basic_refresh" + self.mail_label_option = "select2-result-label-19" + self.contact_selectall_id = "CIVICRM_QFID_ts_all_4" + self.contact_dropdown_id = "select2-chosen-4" + # Using this to count amount of people in the exported pdf + # This will fail if someone is added that doesn't have a UK adddress + self.pdf_search_string = "UNITED KINGDOM" + + def _test(self): + self.browser.get(self.search_url) + group_dropdown = self.find_element_by_id(self.group_dropdown) + group_dropdown.click() + group_dropdown.send_keys("Steering Committee" + Keys.ENTER) + self.find_element_by_id(self.search_button).click() + self.wait_until_visible( + (By.ID, "alpha-filter") + ) #wait for table to load + self.debug("table of results has loaded") + # TODO: Refactor this in base class as a helper function because we do this in another test + results_text = self.find_element_by_css_selector( + ".form-layout-compressed > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(2) > label:nth-child(2)" + ).text + matches = re.findall( + r"(\d+)", results_text + ) # Should just be one match in normal cases + result_no = int(matches[0]) + self.debug("exporting results using the magic dropdown") + self.find_element_by_id(self.contact_selectall_id).click() + self.find_element_by_id(self.contact_dropdown_id).click() + self.find_element_by_id(self.mail_label_option).click() + + self.wait_until_visible((By.CSS_SELECTOR, ".crm-block")) + # By omitting the field, we are effectively disabling the do not mail filter + data = { + "_qf_default": "Label:submit", + # Smallest labels, will show 24 contacts on one page + "label_name": "3475", + "location_type_id": "", + "_qf_Label_submit": "Make+Mailing+Labels" + } + res = self.download(data) + pdf_text = extract_text(io.BytesIO(res.content)) + label_count = pdf_text.count(self.pdf_search_string) + if result_no == label_count: + self.passed( + "no missing mailing labels from pdf export. Expected: %d, Actual %d" + % (result_no, label_count) + ) + else: + self.failed( + "missing mailing labels from the pdf export. Expected: %d, Actual %d" + % (result_no, label_count) + ) + + def test(self): + try: + self.login() + self._test() + finally: + self.logout() + self.browser.close() diff --git a/main.py b/main.py index b4b5a95..7befc09 100644 --- a/main.py +++ b/main.py @@ -1,429 +1,9 @@ import argparse -import csv -import logging -import io -import os -import re -import urllib.parse as urlparse -from urllib.parse import parse_qs - -from pdfminer.high_level import extract_text -import requests -from rich.logging import RichHandler -from rich.console import Console -from selenium import webdriver -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support.select import Select - - -class BaseTester: - """ - Base class for all test suites - - One caveat: This class defines a pseudo function "_test" which is called by "_test_all" - Overloading classes are to define their own tests to be run by selenium after logging in - Then they are to call "_test_all" which will run these tests in a loop with given variables - This allows the test to be run multiple times with different terms to make sure the test covers all bases. - """ - def __init__(self, user: str, passwd: str, dev: bool, show_browser: bool): - self.console = Console() - # Disable pdfminers logger cause its annoying as fuck and provides no value - logging.getLogger("pdfminer").setLevel(100) - logging.basicConfig( - level="INFO", - format="%(message)s", - datefmt="[%X]", - handlers=[RichHandler()] - ) - self.log = logging.getLogger("civiCRM-tester") - self.user = user - self.passwd = passwd - if dev: - self.base_url = "https://crm-dev.caat.org.uk" - else: - self.base_url = "https://crm.caat.org.uk" - - firefox_options = webdriver.FirefoxOptions() - firefox_options.headless = not show_browser - self.browser = webdriver.Firefox(options=firefox_options) - self.wait = WebDriverWait(self.browser, 20) - - def debug(self, msg: str): - """Wrapper for logging debug levels for rich output""" - return self.log.debug(msg, extra={"markup": True}) - - def info(self, msg: str): - """Wrapper for logging info levels for rich output""" - return self.log.info(msg, extra={"markup": True}) - - def warn(self, msg: str): - """Wrapper for logging warn levels for rich output""" - return self.log.warning(msg, extra={"markup": True}) - - def error(self, msg: str): - """Wrapper for logging error levels for rich output""" - return self.log.error(msg, extra={"markup": True}) - - def critical(self, msg: str): - """Wrapper for logging critical levels for rich output""" - return self.log.critical(msg, extra={"markup": True}) - - def passed(self, msg: str): - """Wrapper for logging passed tests""" - return self.info("[reverse green]PASSED[/reverse green] %s" % msg) - - def issue(self, msg: str): - """ - Wrapper for logging tests with issues - This is used for tests where the result is inconclusive due to some issue that is neither a fail nor pass. - Usually requires human interaction - """ - return self.warn("[reverse yellow]ISSUE[/reverse yellow] %s" % msg) - - def failed(self, msg: str): - """Wrapper for logging failed tests""" - return self.info("[reverse red]FAILED[/reverse red] %s" % msg) - - def title(self, test_name: str): - """Create title header for new suite of tests""" - return self.info("[cyan]%s[/cyan]" % test_name) - - def desc(self, test_desc: str): - """Log test description of upcoming number of tests""" - return self.info("%s" % test_desc) - - def login(self): - """ Login to civicrm so we can continue with the proper cookies """ - self.browser.get(self.base_url) - self.debug("Logging in as %s @ %s" % (self.user, self.base_url)) - username = self.browser.find_element_by_id("edit-name") - password = self.browser.find_element_by_id("edit-pass") - submit = self.browser.find_element_by_id("edit-submit") - username.send_keys(self.user) - password.send_keys(self.passwd) - submit.click() - - # Wait for the js elements load so we know the cookies are good. - # Waits for "Recent Items" part of sidebar which is unique when logged in - self.wait.until( - EC.visibility_of_element_located((By.ID, "block-civicrm-2")) - ) - self.debug("Successfully logged in as %s" % self.user) - - def logout(self): - """Log browser out of civicrm to reset our test environment""" - self.browser.get(self.base_url + "/user/logout") - # Wait for the next page to load to finish logging out - self.wait.until( - EC.visibility_of_element_located((By.ID, "tabs-wrapper")) - ) - - def _test(self, *args): - """Placeholder to be overwritten by overloading classes""" - - def _test_all(self, test_strings: tuple[str]): - """Loops testing over the given terms""" - try: - self.login() - for term in test_strings: - self._test(term) - finally: - self.logout() - self.browser.close() - - def find_element_by_id(self, *args, **kwargs): - """Alias for browser.find_element_by_id""" - return self.browser.find_element_by_id(*args, **kwargs) - - def find_element_by_css_selector(self, *args, **kwargs): - """Alias for browser.find_element_by_css_selector""" - return self.browser.find_element_by_css_selector(*args, **kwargs) - - def wait_until_visible(self, locator): - """Alias for using inbuilt wait object for wait.until(EC.visibility_of_element_located)""" - return self.wait.until(EC.visibility_of_element_located(locator)) - - def wait_until_clickable(self, locator): - """Alias for using inbuilt wait object for wait.until(EC.element_to_be_clickable)""" - return self.wait.until(EC.element_to_be_clickable(locator)) - - -class SearchExportTester(BaseTester): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def _get_export_id(self) -> str: - """Parses url to get the param used to ID what search we are currently doing""" - export_page_url = self.browser.current_url - parsed = urlparse.urlparse(export_page_url) - qf_key = parse_qs(parsed.query)['qfKey'] - self.debug("got qf_key '%s' from url" % qf_key) - return qf_key - - def download(self, data: dict) -> requests.Response: - """ - Download exports from searches manually using requests - This is because managing downloads within Selenium is a nightmare - Function tries to replicate the download within the browser as much as possible - """ - session_cookie = {} - for cookie in self.browser.get_cookies(): - if re.findall(r"^SSESS.*", cookie.get("name")): - session_cookie = cookie - self.debug("session cookie: %s" % session_cookie) - if not session_cookie: - self.critical("NO SESSION COOKIE FOUND. Are you logged in?") - raise RuntimeError("No session cookie found.") - - session = requests.Session() - session.cookies.update( - {session_cookie["name"]: session_cookie["value"]} - ) - # Most of this data is replicating the export settings so that the response is the csv data - data = { - "qfKey": self._get_export_id(), - "entryURL": self.base_url + "/civicrm/contact/search", - **data # Add provided export data required for specific export requests - } - res = session.request( - "POST", self.base_url + "/civicrm/contact/search", data=data - ) - return res - - -class ContactExport(SearchExportTester): - """Tests if exporting contacts from a search returns a CSV file with all contacts.""" - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.title("Contact Export") - self.desc( - "Testing if exporting contacts from a search returns a CSV file with all expected contacts." - ) - self.search_url = self.base_url + "/civicrm/contact/search" - - self.contact_selectall_id = "CIVICRM_QFID_ts_all_4" - self.contact_dropdown_id = "select2-chosen-4" - self.contact_exportoption_id = "select2-result-label-15" - - def download_csv(self): - # Data of the request that is specific for this export and not genertic like qr_key - data = { - "_qf_Select_next": "Continue", - "exportOption": 1, - "mergeOption": 0, - "postal_greeting": 1, - "addressee": 1, - } - return self.download(data) - - def calculate_exported_contacts_number(self) -> int: - """ - Downloads csv, opens it in a string buffer using StringIO and passes it to the csv lib - Counts number of rows (excl. header) and returns that value - """ - res = self.download_csv() - csv_file = io.StringIO(res.text) - - # Dict reader should remove headers - exported_csv = csv.DictReader(csv_file) - exported_number_exports = sum(1 for row in exported_csv) - self.debug( - "found %d rows in exported csv excluding header row" % - exported_number_exports - ) - - return exported_number_exports - - def _test(self, search_term: str): - """ - Test Description: - Go to the contact search url - Search for the search term - Select all contacts and set them to export - Get the login cookie and search ID and download the export manually with requests - Save the file in tmp - Read it to check the number of exported contacts is the same as reported in the UI - """ - self.browser.get(self.search_url) - search_box = self.find_element_by_id("sort_name") - search_box.send_keys(search_term) - search_box.send_keys(Keys.ENTER) - self.debug("searching for contacts with term '%s'" % search_term) - self.wait_until_visible( - (By.ID, "alpha-filter") - ) #wait for table to load - self.debug("table of results has loaded") - - results_text = self.find_element_by_css_selector( - ".form-layout-compressed > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(2) > label:nth-child(2)" - ).text - matches = re.findall( - r"(\d+)", results_text - ) # Should just be one match in normal cases - result_no = int(matches[0]) - - self.debug("exporting results using the magic dropdown") - self.find_element_by_id(self.contact_selectall_id).click() - self.find_element_by_id(self.contact_dropdown_id).click() - self.find_element_by_id(self.contact_exportoption_id).click() - self.wait_until_visible((By.CSS_SELECTOR, ".crm-block")) - - exported_number_exports = self.calculate_exported_contacts_number() - if exported_number_exports == (result_no): - self.passed( - "Number of expected contact exports for '%s' matches actual number of exports - Expected: %d, Actual: %d" - % (search_term, result_no, exported_number_exports) - ) - else: - self.failed( - "Number of expected contact exports for '%s' WAS NOT EQUAL to actual number of exports - Expected: %d, Actual: %d" - % (search_term, result_no, exported_number_exports) - ) - - def test_hardcoded_search_terms(self): - """Loops over the test with three hardcoded search terms""" - search_terms = ("John", "e", "Smith") - self._test_all(search_terms) - - -class ActivitiesTab(BaseTester): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.title("Activities Tab") - self.desc("Testing if a contacts activities tab displays all activies") - self.contact_page = self.base_url + "/civicrm/contact/view/?reset=1&cid={}" - - def _test(self, cid: str): - self.debug("loading contact page for CID %s" % cid) - self.browser.get(self.contact_page.format(cid)) - self.wait_until_visible((By.ID, "ui-id-10")) - # Contact page as loaded - activities_tab_button = self.find_element_by_id("ui-id-10") - num_element = activities_tab_button.find_element_by_tag_name("em") - num_of_activ = int(num_element.text) - activities_tab_button.click() - table_row_selector = ( - By.CSS_SELECTOR, "#DataTables_Table_0 > tbody tr" - ) - self.debug( - "clicked activities button and waiting for activities page to load" - ) - self.wait_until_visible(table_row_selector) - # Activities page as now loaded - table_length_dropdown = Select( - self.browser.find_element_by_xpath( - "//select[@name='DataTables_Table_0_length']" - ) - ) - table_length_dropdown.select_by_visible_text("100") - self.wait_until_clickable(table_row_selector) - num_of_rows = len( - self.browser.find_elements_by_css_selector("tr.crm-entity") - ) - if num_of_activ == num_of_rows: - self.passed( - "expected number of activities found in activity table for CID %s. Expected: %d, Actual %d" - % (cid, num_of_activ, num_of_rows) - ) - elif num_of_activ > 100 and num_of_rows == 100: - self.issue( - "Number of activities for CID %s is above 100. This is the max amount the table can display. The table is displaying 100 entries. Pagination is not supported by the script." - % cid - ) - else: - self.failed( - "Number of activities is lower than expected for CID %s. Most likely didn't load properly Expected: %d, Actual %d" - % (cid, num_of_activ, num_of_rows) - ) - - def test_all_hardcoded_contacts(self): - """Loops self.test with all hardcoded contact ID's - Using MP's as MP's are public knowledge and often have activities within the crm system - names provided in comments here for debugging purposes - """ - cid_da = "42219" # Debbie Abrahams - cid_kh = "82163" # Kate Hollern - cid_na = "42269" # Nigel Addams - #cid_db = "43193" Use to test 100 max limit - cid_tuple = (cid_da, cid_kh, cid_na) - self._test_all(cid_tuple) - - -class SteeringCommitteePrintLabels(SearchExportTester): - """Tests the pdf labels for the SteeringCommitee show all contacts names""" - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.title("Steering Committee Print Labels") - self.desc( - "Testing the pdf labels for the SteeringCommittee show all contacts names" - ) - self.search_url = self.base_url + "/civicrm/contact/search" - self.group_dropdown = "s2id_autogen2" - self.search_button = "_qf_Basic_refresh" - self.mail_label_option = "select2-result-label-19" - self.contact_selectall_id = "CIVICRM_QFID_ts_all_4" - self.contact_dropdown_id = "select2-chosen-4" - # Using this to count amount of people in the exported pdf - # This will fail if someone is added that doesn't have a UK adddress - self.pdf_search_string = "UNITED KINGDOM" - - def _test(self): - self.browser.get(self.search_url) - group_dropdown = self.find_element_by_id(self.group_dropdown) - group_dropdown.click() - group_dropdown.send_keys("Steering Committee" + Keys.ENTER) - self.find_element_by_id(self.search_button).click() - self.wait_until_visible( - (By.ID, "alpha-filter") - ) #wait for table to load - self.debug("table of results has loaded") - # TODO: Refactor this in base class as a helper function because we do this in another test - results_text = self.find_element_by_css_selector( - ".form-layout-compressed > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(2) > label:nth-child(2)" - ).text - matches = re.findall( - r"(\d+)", results_text - ) # Should just be one match in normal cases - result_no = int(matches[0]) - self.debug("exporting results using the magic dropdown") - self.find_element_by_id(self.contact_selectall_id).click() - self.find_element_by_id(self.contact_dropdown_id).click() - self.find_element_by_id(self.mail_label_option).click() - - self.wait_until_visible((By.CSS_SELECTOR, ".crm-block")) - # By omitting the field, we are effectively disabling the do not mail filter - data = { - "_qf_default": "Label:submit", - # Smallest labels, will show 24 contacts on one page - "label_name": "3475", - "location_type_id": "", - "_qf_Label_submit": "Make+Mailing+Labels" - } - res = self.download(data) - pdf_text = extract_text(io.BytesIO(res.content)) - label_count = pdf_text.count(self.pdf_search_string) - if result_no == label_count: - self.passed( - "no missing mailing labels from pdf export. Expected: %d, Actual %d" - % (result_no, label_count) - ) - else: - self.failed( - "missing mailing labels from the pdf export. Expected: %d, Actual %d" - % (result_no, label_count) - ) - - def test(self): - try: - self.login() - self._test() - finally: - self.logout() - self.browser.close() +from civicrm_tester.activities_tab import ActivitiesTab +from civicrm_tester.contact_export import ContactExport +from civicrm_tester.steeringcommittee_print_labels import \ + SteeringCommitteePrintLabels if __name__ == "__main__": parser = argparse.ArgumentParser(description="") @@ -452,9 +32,9 @@ if __name__ == "__main__": cl_arg = ( arguments.user, arguments.passwd, arguments.dev, arguments.show_browser ) - SteeringCommitteePrintLabels(*cl_arg).test() ActivitiesTab(*cl_arg).test_all_hardcoded_contacts() ContactExport(*cl_arg).test_hardcoded_search_terms() + SteeringCommitteePrintLabels(*cl_arg).test() # Mailing list # Load mailing list test and enter test data