refactor: file structure change to multiple files

This commit is contained in:
Roxie Gibson 2021-01-25 20:48:43 +00:00
parent 9bca740133
commit abdcce6042
Signed by untrusted user: roxxers
GPG Key ID: 5D0140EDEE123F4D
6 changed files with 440 additions and 425 deletions

View File

View File

@ -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)

184
civicrm_tester/base.py Normal file
View File

@ -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

View File

@ -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)

View File

@ -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()

430
main.py
View File

@ -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