From 7d2b3ef15bcc80793cd366396a25f9b6528e5da5 Mon Sep 17 00:00:00 2001 From: "virgil.sangerean" Date: Fri, 28 Mar 2025 10:29:09 +0200 Subject: [PATCH 1/4] vs/testrail-ex --- modules/testrail.py | 48 +++++++++++---------- modules/testrail_integration.py | 20 ++++++--- modules/testrail_script.py | 75 +++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 27 deletions(-) create mode 100644 modules/testrail_script.py diff --git a/modules/testrail.py b/modules/testrail.py index 1a46bf90..14aaf170 100644 --- a/modules/testrail.py +++ b/modules/testrail.py @@ -104,7 +104,7 @@ def __send_request(self, method, uri, data): try: error = response.json() except ( - requests.exceptions.HTTPError + requests.exceptions.HTTPError ): # response.content not formatted as JSON error = str(response.content) raise APIError( @@ -176,7 +176,7 @@ def update_test_case(self, case_id, **kwargs): return self.client.send_post(f"update_case/{case_id}", **kwargs) def create_test_run_on_plan_entry( - self, plan_id, entry_id, config_ids, description=None, case_ids=None + self, plan_id, entry_id, config_ids, description=None, case_ids=None ): """ Add a test run on an entry (subplan) associated with a plan. @@ -259,12 +259,12 @@ def matching_custom_field(self, name): return None def create_new_plan( - self, - testrail_project_id, - name, - description=None, - milestone_id=None, - entries=None, + self, + testrail_project_id, + name, + description=None, + milestone_id=None, + entries=None, ): """ Create a new test plan (on a milestone). @@ -297,14 +297,14 @@ def create_new_plan( return self.client.send_post(f"/add_plan/{testrail_project_id}", payload) def create_new_plan_entry( - self, - plan_id, - suite_id, - name=None, - description=None, - case_ids=None, - config_ids=None, - runs=None, + self, + plan_id, + suite_id, + name=None, + description=None, + case_ids=None, + config_ids=None, + runs=None, ): """ Create a new entry (subplan) on a plan. @@ -380,12 +380,12 @@ def update_case_field(self, case_id, field_id, content): return self.client.send_post(f"update_case/{case_id}", data) def update_test_cases( - self, - testrail_project_id, - testrail_run_id, - testrail_suite_id, - test_case_ids=[], - status="passed", + self, + testrail_project_id, + testrail_run_id, + testrail_suite_id, + test_case_ids=[], + status="passed", ): """Given a project id, a run id, and a suite id, for each case given a status, update the test objects with the correct status code""" @@ -476,6 +476,10 @@ def _get_full_plan(self, plan_id): def _get_case_fields(self): return self.client.send_get("get_case_fields") + def get_suites(self, project_id): + """Get all suites for project""" + return self.client.send_get(f"get_suites/{project_id}") + def _retry_api_call(self, api_call, *args, max_retries=3, delay=5): """ Retries the given API call up to max_retries times with a delay between attempts. diff --git a/modules/testrail_integration.py b/modules/testrail_integration.py index a7f75af9..15dd40a0 100644 --- a/modules/testrail_integration.py +++ b/modules/testrail_integration.py @@ -172,11 +172,21 @@ def reportable(platform_to_test=None): def testrail_init() -> TestRail: """Connect to a TestRail API session""" - local = os.environ.get("TESTRAIL_BASE_URL").split("/")[2].startswith("127") - tr_session = tr.TestRail( - os.environ.get("TESTRAIL_BASE_URL"), - os.environ.get("TESTRAIL_USERNAME"), - os.environ.get("TESTRAIL_API_KEY"), + base_url = os.environ.get("TESTRAIL_BASE_URL") + username = os.environ.get("TESTRAIL_USERNAME") + api_key = os.environ.get("TESTRAIL_API_KEY") + + if not base_url or not username or not api_key: + raise ValueError( + "Missing TestRail credentials. Check your api_credentials.env file." + ) + + local = base_url.split("/")[2].startswith("127") + + tr_session = TestRail( + base_url, + username, + api_key, local, ) return tr_session diff --git a/modules/testrail_script.py b/modules/testrail_script.py new file mode 100644 index 00000000..5b2275f3 --- /dev/null +++ b/modules/testrail_script.py @@ -0,0 +1,75 @@ +import logging +import os +from dotenv import load_dotenv +from modules.testrail_integration import testrail_init + +# Set up logging +logging.basicConfig( + level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s" +) + +# Load env file from project root +script_dir = os.path.dirname(__file__) +project_root = os.path.abspath(os.path.join(script_dir, "..")) +env_file_path = os.path.join(project_root, "api_credentials.env") +load_dotenv(dotenv_path=env_file_path) + +MY_SUITES = ["Printing UI", "Profile", "Reader View"] +PROJECT_ID = 17 + +DRY_RUN = True # Set to False when performing actual run + +def main(): + # Read credentials from environment + base_url = os.environ.get("TESTRAIL_BASE_URL") + username = os.environ.get("TESTRAIL_USERNAME") + api_key = os.environ.get("TESTRAIL_API_KEY") + + if not all([base_url, username, api_key]): + logging.error("Missing TestRail credentials. Check your .env file.") + return + + logging.info(f"Loaded TestRail credentials for user: {username}") + logging.info(f"TestRail Base URL: {base_url}") + + tr = testrail_init() + + logging.info(f"Fetching suite IDs for suites: {MY_SUITES}") + suites = list(tr.get_suites(PROJECT_ID)) + suite_ids = [ + suite["id"] + for suite in suites + if suite["name"] in MY_SUITES + ] + logging.info(f"Suite IDs to process: {suite_ids}") + + case_ids = [] + for suite_id in suite_ids: + val = tr._get_test_cases(PROJECT_ID, suite_id) + if val["size"] < val["limit"]: + matching_cases = [ + case for case in val["cases"] if case["custom_automated_test_names"] + ] + matching_case_ids = [case["id"] for case in matching_cases] + + if DRY_RUN: + logging.info( + f"[DRY RUN] Suite {suite_id} would process cases: {matching_case_ids}" + ) + else: + case_ids.extend(matching_case_ids) + logging.info( + f"Suite {suite_id} processed and added case IDs: {matching_case_ids}" + ) + else: + logging.warning(f"Suite {suite_id} test cases exceed retrieval limit.") + + if DRY_RUN: + logging.info("[DRY RUN] No actual updates performed.") + logging.info(f"[DRY RUN] Total collected case IDs (not updated): {case_ids}") + else: + logging.info(f"Total collected case IDs: {case_ids}") + # Add here actual logic for updates if necessary + +if __name__ == "__main__": + main() \ No newline at end of file From e45b62d1ddf2a10ee00e0330b0972864161aba13 Mon Sep 17 00:00:00 2001 From: "virgil.sangerean" Date: Fri, 28 Mar 2025 10:29:09 +0200 Subject: [PATCH 2/4] vs/testrail-ex --- modules/testrail_script.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/modules/testrail_script.py b/modules/testrail_script.py index 5b2275f3..b95c5201 100644 --- a/modules/testrail_script.py +++ b/modules/testrail_script.py @@ -14,10 +14,12 @@ env_file_path = os.path.join(project_root, "api_credentials.env") load_dotenv(dotenv_path=env_file_path) -MY_SUITES = ["Printing UI", "Profile", "Reader View"] +MY_SUITES = ["Printing", "Startup and Profile", "Reader View"] PROJECT_ID = 17 -DRY_RUN = True # Set to False when performing actual run +FIELD_TO_UPDATE = "custom_automated_test_status" +NEW_FIELD_VALUE = 1 + def main(): # Read credentials from environment @@ -48,28 +50,25 @@ def main(): val = tr._get_test_cases(PROJECT_ID, suite_id) if val["size"] < val["limit"]: matching_cases = [ - case for case in val["cases"] if case["custom_automated_test_names"] + case for case in val["cases"] + if case.get("custom_automated_test_names") and case.get("custom_automation_status") == 4 ] matching_case_ids = [case["id"] for case in matching_cases] - if DRY_RUN: - logging.info( - f"[DRY RUN] Suite {suite_id} would process cases: {matching_case_ids}" - ) - else: - case_ids.extend(matching_case_ids) - logging.info( - f"Suite {suite_id} processed and added case IDs: {matching_case_ids}" - ) + case_ids.extend(matching_case_ids) + logging.info( + f"Suite {suite_id} processed and added case IDs: {matching_case_ids}" + ) else: logging.warning(f"Suite {suite_id} test cases exceed retrieval limit.") - if DRY_RUN: - logging.info("[DRY RUN] No actual updates performed.") - logging.info(f"[DRY RUN] Total collected case IDs (not updated): {case_ids}") - else: - logging.info(f"Total collected case IDs: {case_ids}") - # Add here actual logic for updates if necessary + logging.info(f"Total collected case IDs: {case_ids}") + + # Perform updates to TestRail + for case_id in case_ids: + response = tr.update_test_case(case_id, data={FIELD_TO_UPDATE: NEW_FIELD_VALUE}) + logging.info(f"Updated case ID {case_id}: {response}") + if __name__ == "__main__": main() \ No newline at end of file From 52dd8b2bd42db75a2192eb08b30ee3cfc3e63d80 Mon Sep 17 00:00:00 2001 From: "virgil.sangerean" Date: Fri, 28 Mar 2025 10:29:09 +0200 Subject: [PATCH 3/4] vs/testrail-ex --- modules/testrail_script.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/modules/testrail_script.py b/modules/testrail_script.py index 5b2275f3..c2ebe858 100644 --- a/modules/testrail_script.py +++ b/modules/testrail_script.py @@ -14,10 +14,12 @@ env_file_path = os.path.join(project_root, "api_credentials.env") load_dotenv(dotenv_path=env_file_path) -MY_SUITES = ["Printing UI", "Profile", "Reader View"] +MY_SUITES = ["Printing", "Startup and Profile", "Reader View"] PROJECT_ID = 17 -DRY_RUN = True # Set to False when performing actual run +# Define what field to update and its new value +FIELD_TO_UPDATE = "custom_automation_coverage" +NEW_FIELD_VALUE = 3 # 3 might represent 'Covered' def main(): # Read credentials from environment @@ -48,28 +50,25 @@ def main(): val = tr._get_test_cases(PROJECT_ID, suite_id) if val["size"] < val["limit"]: matching_cases = [ - case for case in val["cases"] if case["custom_automated_test_names"] + case for case in val["cases"] + if case.get("custom_automated_test_names") and case.get("custom_automation_status") == 4 ] matching_case_ids = [case["id"] for case in matching_cases] - if DRY_RUN: - logging.info( - f"[DRY RUN] Suite {suite_id} would process cases: {matching_case_ids}" - ) - else: - case_ids.extend(matching_case_ids) - logging.info( - f"Suite {suite_id} processed and added case IDs: {matching_case_ids}" - ) + case_ids.extend(matching_case_ids) + logging.info( + f"Suite {suite_id} processed and added case IDs: {matching_case_ids}" + ) else: logging.warning(f"Suite {suite_id} test cases exceed retrieval limit.") - if DRY_RUN: - logging.info("[DRY RUN] No actual updates performed.") - logging.info(f"[DRY RUN] Total collected case IDs (not updated): {case_ids}") - else: - logging.info(f"Total collected case IDs: {case_ids}") - # Add here actual logic for updates if necessary + logging.info(f"Total collected case IDs: {case_ids}") + + # Perform updates to TestRail + for case_id in case_ids: + response = tr.update_case_field(case_id, FIELD_TO_UPDATE, NEW_FIELD_VALUE) + logging.info(f"Updated case ID {case_id}: {response}") + if __name__ == "__main__": - main() \ No newline at end of file + main() From 31f63685d6df5e9f518dfbe5da2fe4bb3a3422e5 Mon Sep 17 00:00:00 2001 From: vsangereanMOZ Date: Tue, 15 Apr 2025 14:08:27 +0300 Subject: [PATCH 4/4] Delete api_credentials.env --- api_credentials.env | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 api_credentials.env diff --git a/api_credentials.env b/api_credentials.env deleted file mode 100644 index eee581a8..00000000 --- a/api_credentials.env +++ /dev/null @@ -1,3 +0,0 @@ -TESTRAIL_BASE_URL=https://mozilla.testrail.io -TESTRAIL_USERNAME=vsangerean@mozilla.com -TESTRAIL_API_KEY=kdnzYnWr4DYKE5FyhA7F-.UY8D4ufRRrXzODAqEuP