tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

testrail_api.py (8855B)


      1 #!/usr/bin/env python3
      2 
      3 # This Source Code Form is subject to the terms of the Mozilla Public
      4 # License, v. 2.0. If a copy of the MPL was not distributed with this
      5 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
      6 # TestRail API Wrapper Module
      7 
      8 # This module provides methods to interact with the TestRail API.
      9 # API Version: v2
     10 # TestRail Version: 8.0.5 https://mozilla.testrail.io
     11 
     12 # For detailed information on the TestRail API, please refer to the official documentation:
     13 # https://support.testrail.com/hc/en-us/sections/7077185274644-API-reference
     14 """
     15 This module provides a TestRail class for interfacing with the TestRail API, enabling the creation and management of test milestones, test runs, and updating test cases. It facilitates automation and integration of TestRail functionalities into testing workflows, particularly for projects requiring automated test management and reporting.
     16 
     17 The TestRail class encapsulates methods to interact with TestRail's API, including creating milestones and test runs, updating test cases, and checking the existence of milestones. It also features a method to retry API calls, enhancing the robustness of network interactions.
     18 
     19 Key Components:
     20 - TestRail Class: A class providing methods for interacting with TestRail's API.
     21  - create_milestone: Create a new milestone in a TestRail project.
     22  - create_milestone_and_test_runs: Create a milestone and associated test runs for multiple devices in a project.
     23  - create_test_run: Create a test run within a TestRail project.
     24  - does_milestone_exist: Check if a milestone already exists in a TestRail project.
     25  - update_test_cases_to_passed: Update the status of test cases to 'passed' in a test run.
     26 - Private Methods: Utility methods for internal use to fetch test cases, update test run results, and retrieve milestones.
     27 - Retry Mechanism: A method to retry API calls with a specified number of attempts and delay, improving reliability in case of intermittent network issues.
     28 
     29 Usage:
     30 This module is intended to be used as part of a larger automated testing system, where integration with TestRail is required for test management and reporting.
     31 
     32 """
     33 
     34 import os
     35 import sys
     36 import time
     37 
     38 # Ensure the directory containing this script is in Python's search path
     39 script_directory = os.path.dirname(os.path.abspath(__file__))
     40 if script_directory not in sys.path:
     41    sys.path.append(script_directory)
     42 
     43 from testrail_conn import APIClient
     44 
     45 
     46 class TestRail:
     47    def __init__(self, host, username, password):
     48        if not all([host, username, password]):
     49            raise ValueError("TestRail host, username, and password must be provided.")
     50        self.client = APIClient(host)
     51        self.client.user = username
     52        self.client.password = password
     53 
     54    # Public Methods
     55 
     56    def create_milestone(self, project_id, title, description):
     57        if not all([project_id, title, description]):
     58            raise ValueError("Project ID, title, and description must be provided.")
     59        data = {"name": title, "description": description}
     60        return self.client.send_post(f"add_milestone/{project_id}", data)
     61 
     62    def create_test_run(
     63        self,
     64        project_id,
     65        milestone_id,
     66        test_run_name,
     67        suite_id,
     68    ):
     69        if not all([project_id, milestone_id, test_run_name, suite_id]):
     70            raise ValueError(
     71                "Project ID, milestone ID, test run name, and suite ID must be provided."
     72            )
     73        data = {
     74            "name": test_run_name,
     75            "milestone_id": milestone_id,
     76            "suite_id": suite_id,
     77        }
     78        return self.client.send_post(f"add_run/{project_id}", data)
     79 
     80    def does_milestone_exist(self, project_id, milestone_name):
     81        """
     82        Check if a milestone with a specific name exists among open milestones.
     83 
     84        Args:
     85            project_id (int): ID of the project.
     86            milestone_name (str): Name of the milestone to search for.
     87 
     88        Returns:
     89            bool: True if the milestone exists, False otherwise.
     90        """
     91        if not all([project_id, milestone_name]):
     92            raise ValueError("Project ID and milestone name must be provided.")
     93 
     94        # Fetch open milestones (is_completed=False)
     95        response = self._get_milestones(project_id, is_completed=False)
     96        milestones = response.get("milestones", [])
     97        # Check for the milestone name
     98        return any(milestone["name"] == milestone_name for milestone in milestones)
     99 
    100    def update_test_run_tests(self, test_run_id, test_status):
    101        if not all([test_run_id, test_status]):
    102            raise ValueError("Test run ID and test status must be provided.")
    103        tests = self._get_tests(test_run_id)
    104        data = {
    105            "results": [
    106                {"test_id": test["id"], "status_id": test_status} for test in tests
    107            ]
    108        }
    109        return self.client.send_post(f"add_results/{test_run_id}", data)
    110 
    111    # Private Methods
    112 
    113    def _get_test_cases(self, project_id, suite_id):
    114        if not all([project_id, suite_id]):
    115            raise ValueError("Project ID and suite ID must be provided.")
    116        return self.client.send_get(f"get_cases/{project_id}&suite_id={suite_id}")[
    117            "cases"
    118        ]
    119 
    120    def _get_milestone(self, milestone_id):
    121        if not milestone_id:
    122            raise ValueError("Milestone ID must be provided.")
    123        return self.client.send_get(f"get_milestone/{milestone_id}")
    124 
    125    def _get_milestones(self, project_id, **filters):
    126        """
    127        Retrieves milestones for a given project with optional filters.
    128 
    129        Args:
    130            project_id (int): The ID of the project.
    131            **filters: Arbitrary keyword arguments representing API filters.
    132 
    133        Available Filters:
    134            is_completed (bool or int):
    135                - Set to True or 1 to return completed milestones only.
    136                - Set to False or 0 to return open (active/upcoming) milestones only.
    137 
    138            is_started (bool or int):
    139                - Set to True or 1 to return started milestones only.
    140                - Set to False or 0 to return upcoming milestones only.
    141 
    142            limit (int):
    143                - The number of milestones the response should return.
    144                - The default response size is 250 milestones.
    145 
    146            offset (int):
    147                - Where to start counting the milestones from (the offset).
    148                - Used for pagination.
    149 
    150        Returns:
    151            dict: The API response containing milestones.
    152        """
    153 
    154        if not project_id:
    155            raise ValueError("Project ID must be provided.")
    156 
    157        # Base endpoint
    158        endpoint = f"get_milestones/{project_id}"
    159 
    160        # Process filters
    161        if filters:
    162            # Convert boolean values to integers (API expects 1 or 0)
    163            for key in ["is_completed", "is_started"]:
    164                if key in filters and isinstance(filters[key], bool):
    165                    filters[key] = int(filters[key])
    166 
    167            # Build query parameters
    168            query_params = "&".join(f"{key}={value}" for key, value in filters.items())
    169            endpoint = f"{endpoint}&{query_params}"
    170 
    171        # Make API call
    172        return self.client.send_get(endpoint)
    173 
    174    def _get_tests(self, test_run_id):
    175        if not test_run_id:
    176            raise ValueError("Test run ID must be provided.")
    177        return self.client.send_get(f"get_tests/{test_run_id}")["tests"]
    178 
    179    def _get_test_run(self, test_run_id):
    180        if not test_run_id:
    181            raise ValueError("Test run ID must be provided.")
    182        return self.client.send_get(f"get_run/{test_run_id}")
    183 
    184    def _get_test_runs(self, project_id):
    185        if not project_id:
    186            raise ValueError("Project ID must be provided.")
    187        return self.client.send_get(f"get_runs/{project_id}")["runs"]
    188 
    189    def _get_test_run_results(self, test_run_id):
    190        if not test_run_id:
    191            raise ValueError("Test run ID must be provided.")
    192        return self.client.send_get(f"get_results_for_run/{test_run_id}")["results"]
    193 
    194    def _retry_api_call(self, api_call, *args, max_retries=3, delay=5):
    195        if not all([api_call, args]):
    196            raise ValueError("API call and arguments must be provided.")
    197        """
    198        Retries the given API call up to max_retries times with a delay between attempts.
    199 
    200        :param api_call: The API call method to retry.
    201        :param args: Arguments to pass to the API call.
    202        :param max_retries: Maximum number of retries.
    203        :param delay: Delay between retries in seconds.
    204        """
    205        for attempt in range(max_retries):
    206            try:
    207                return api_call(*args)
    208            except Exception:
    209                if attempt == max_retries - 1:
    210                    raise  # Reraise the last exception
    211                time.sleep(delay)