diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 1c1ef6c2..8596f015 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -6,6 +6,14 @@ This project adheres to [Semantic Versioning](https://semver.org/). Version numb - **MINOR**: New features that are backward-compatible. - **PATCH**: Bug fixes or minor changes that do not affect backward compatibility. +## [1.11.0] + +_released 07-30-2025 + +### Added + - Added feature to optionally close test run using --auto-close-run in add_run command + - Added support for Start and End Date API changes for Test Runs and Test Plans + ## [1.10.1] _released 07-11-2025 diff --git a/README.md b/README.md index afb2e373..b4250ad5 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ trcli ``` You should get something like this: ``` -TestRail CLI v1.10.1 +TestRail CLI v1.11.0 Copyright 2025 Gurock Software GmbH - www.gurock.com Supported and loaded modules: - parse_junit: JUnit XML Files (& Similar) @@ -45,7 +45,7 @@ CLI general reference -------- ```shell $ trcli --help -TestRail CLI v1.10.1 +TestRail CLI v1.11.0 Copyright 2025 Gurock Software GmbH - www.gurock.com Usage: trcli [OPTIONS] COMMAND [ARGS]... @@ -287,7 +287,7 @@ will be used to upload all results into the same test run. ### Reference ```shell $ trcli add_run --help -TestRail CLI v1.10.1 +TestRail CLI v1.11.0 Copyright 2025 Gurock Software GmbH - www.gurock.com Usage: trcli add_run [OPTIONS] @@ -298,9 +298,12 @@ Options: --run-description Summary text to be added to the test run. --milestone-id Milestone ID to which the Test Run should be associated to. [x>=1] + --run-start-date The expected or scheduled start date of this test run in MM/DD/YYYY format + --run-end-date The expected or scheduled end date of this test run in MM/DD/YYYY format --run-assigned-to-id The ID of the user the test run should be assigned to. [x>=1] --run-include-all Use this option to include all test cases in this test run. + --auto-close-run Use this option to automatically close the created run. --run-case-ids Comma separated list of test case IDs to include in the test run (i.e.: 1,2,3,4). --run-refs A comma-separated list of references/requirements @@ -326,7 +329,7 @@ providing you with a solid base of test cases, which you can further expand on T ### Reference ```shell $ trcli parse_openapi --help -TestRail CLI v1.10.1 +TestRail CLI v1.11.0 Copyright 2025 Gurock Software GmbH - www.gurock.com Usage: trcli parse_openapi [OPTIONS] diff --git a/tests/test_project_based_client.py b/tests/test_project_based_client.py index 6d45c1fa..a0efc005 100644 --- a/tests/test_project_based_client.py +++ b/tests/test_project_based_client.py @@ -390,6 +390,7 @@ def test_create_or_update_test_run_calls_add_run(self, project_based_client_data project_based_client, ) = project_based_client_data_provider environment.run_id = None + environment.auto_close_run = False api_request_handler.add_run.return_value = (1, "") project_based_client.resolve_project() run_id, error_message = project_based_client.create_or_update_test_run() @@ -411,6 +412,7 @@ def test_create_or_update_test_run_calls_update_run(self, project_based_client_d project_based_client, ) = project_based_client_data_provider environment.run_id = 1 + environment.auto_close_run = False api_request_handler.update_run.return_value = (1, "") project_based_client.resolve_project() run_id, error_message = project_based_client.create_or_update_test_run() diff --git a/tests/test_results_uploader.py b/tests/test_results_uploader.py index ae1e6f09..235bea5c 100644 --- a/tests/test_results_uploader.py +++ b/tests/test_results_uploader.py @@ -208,16 +208,14 @@ def test_upload_results_successful( 2: mocker.call("Removing unnecessary empty sections that may have been created earlier. ", new_line=False), 3: mocker.call("Removed 1 unused/empty section(s)."), 4: mocker.call("Creating test run. ", new_line=False), - 5: mocker.call("Test run: https://fake_host.com/index.php?/runs/view/100"), - 6: mocker.call("Closing test run. ", new_line=False), + 5: mocker.call("Closing run. ", new_line=False), } else: calls = { 2: mocker.call("Removing unnecessary empty sections that may have been created earlier. ", new_line=False), 3: mocker.call("Removed 1 unused/empty section(s)."), 4: mocker.call("Updating test run. ", new_line=False), - 5: mocker.call("Test run: https://fake_host.com/index.php?/runs/view/101"), - 6: mocker.call("Closing test run. ", new_line=False), + 5: mocker.call("Closing run. ", new_line=False), } results_uploader.upload_results() diff --git a/tests_e2e/test_end2end.py b/tests_e2e/test_end2end.py index bc1b9892..8d18e4fe 100644 --- a/tests_e2e/test_end2end.py +++ b/tests_e2e/test_end2end.py @@ -444,6 +444,25 @@ def test_cli_add_run_upload_results(self): "Submitted 6 test results" ] ) + + def test_cli_add_run_and_plan_with_due_date(self): + output = _run_cmd(f""" +trcli -y \\ + -h {self.TR_INSTANCE} \\ + --project "SA - (DO NOT DELETE) TRCLI-E2E-Tests" \\ + add_run --run-include-all \\ + --title "[CLI-E2E-Tests] ADD RUN TEST: Test Run with Due Date" \\ + --run-start-date "03/01/2030" --run-end-date "03/12/2030" + """) + _assert_contains( + output, + [ + "Creating test run.", + f"Test run: {self.TR_INSTANCE}index.php?/runs/view", + "title: [CLI-E2E-Tests] ADD RUN TEST: Test Run with Due Date" + ] + ) + def bug_test_cli_robot_description_bug(self): output = _run_cmd(f""" diff --git a/trcli/__init__.py b/trcli/__init__.py index a0865bba..f84c53b0 100644 --- a/trcli/__init__.py +++ b/trcli/__init__.py @@ -1 +1 @@ -__version__ = "1.10.1" +__version__ = "1.11.0" diff --git a/trcli/api/api_request_handler.py b/trcli/api/api_request_handler.py index 7e54d943..b5623dff 100644 --- a/trcli/api/api_request_handler.py +++ b/trcli/api/api_request_handler.py @@ -404,6 +404,8 @@ def add_run( project_id: int, run_name: str, milestone_id: int = None, + start_date: str = None, + end_date: str = None, plan_id: int = None, config_ids: List[int] = None, assigned_to_id: int = None, @@ -420,6 +422,8 @@ def add_run( add_run_data = self.data_provider.add_run( run_name, case_ids=case_ids, + start_date=start_date, + end_date=end_date, milestone_id=milestone_id, assigned_to_id=assigned_to_id, include_all=include_all, @@ -443,7 +447,8 @@ def add_run( run_id = response.response_text["runs"][0]["id"] return run_id, response.error_message - def update_run(self, run_id: int, run_name: str, milestone_id: int = None) -> Tuple[dict, str]: + def update_run(self, run_id: int, run_name: str, start_date: str = None, + end_date: str = None, milestone_id: int = None) -> Tuple[dict, str]: """ Updates an existing run :run_id: run id @@ -453,7 +458,8 @@ def update_run(self, run_id: int, run_name: str, milestone_id: int = None) -> Tu run_response = self.client.send_get(f"get_run/{run_id}") existing_description = run_response.response_text.get("description", "") - add_run_data = self.data_provider.add_run(run_name, milestone_id=milestone_id) + add_run_data = self.data_provider.add_run(run_name, start_date=start_date, + end_date=end_date, milestone_id=milestone_id) add_run_data["description"] = existing_description # Retain the current description run_tests, error_message = self.__get_all_tests_in_run(run_id) diff --git a/trcli/api/project_based_client.py b/trcli/api/project_based_client.py index ba230b25..1bf27375 100644 --- a/trcli/api/project_based_client.py +++ b/trcli/api/project_based_client.py @@ -214,6 +214,8 @@ def create_or_update_test_run(self) -> Tuple[int, str]: project_id=self.project.project_id, run_name=self.run_name, milestone_id=self.environment.milestone_id, + start_date=self.environment.run_start_date, + end_date=self.environment.run_end_date, plan_id=self.environment.plan_id, config_ids=self.environment.config_ids, assigned_to_id=self.environment.run_assigned_to_id, @@ -228,6 +230,13 @@ def create_or_update_test_run(self) -> Tuple[int, str]: run, error_message = self.api_request_handler.update_run( run_id, self.run_name, self.environment.milestone_id ) + if self.environment.auto_close_run: + self.environment.log("Closing run. ", new_line=False) + close_run, error_message = self.api_request_handler.close_run(run_id) + if close_run: + self.environment.log("Run closed successfully.") + else: + self.environment.elog(f"Failed to close run: {error_message}") if error_message: self.environment.elog("\n" + error_message) else: diff --git a/trcli/cli.py b/trcli/cli.py index a38b9f94..3fa81ccb 100755 --- a/trcli/cli.py +++ b/trcli/cli.py @@ -51,6 +51,8 @@ def __init__(self, cmd="parse_junit"): self.plan_id = None self.config_ids = None self.milestone_id = None + self.run_start_date = None + self.run_end_date = None self.section_id = None self.auto_creation_response = None self.silent = None @@ -65,6 +67,7 @@ def __init__(self, cmd="parse_junit"): self.run_assigned_to_id = None self.run_case_ids = None self.run_include_all = None + self.auto_close_run = None self.run_refs = None self.proxy = None # Add proxy related attributes self.noproxy = None diff --git a/trcli/commands/cmd_add_run.py b/trcli/commands/cmd_add_run.py index c618d376..b2d270b4 100644 --- a/trcli/commands/cmd_add_run.py +++ b/trcli/commands/cmd_add_run.py @@ -14,6 +14,8 @@ def print_config(env: Environment): f"\n> Suite ID: {env.suite_id}" f"\n> Description: {env.run_description}" f"\n> Milestone ID: {env.milestone_id}" + f"\n> Start Date: {env.run_start_date}" + f"\n> End Date: {env.run_end_date}" f"\n> Assigned To ID: {env.run_assigned_to_id}" f"\n> Include All: {env.run_include_all}" f"\n> Case IDs: {env.run_case_ids}" @@ -54,6 +56,20 @@ def write_run_to_file(environment: Environment, run_id: int): metavar="", help="Milestone ID to which the Test Run should be associated to.", ) +@click.option( + "--run-start-date", + metavar="", + default=None, + type=lambda x: [int(i) for i in x.split("/") if len(x.split("/")) == 3], + help="The expected or scheduled start date of this test run in MM/DD/YYYY format" +) +@click.option( + "--run-end-date", + metavar="", + default=None, + type=lambda x: [int(i) for i in x.split("/") if len(x.split("/")) == 3], + help="The expected or scheduled end date of this test run in MM/DD/YYYY format" +) @click.option( "--run-assigned-to-id", type=click.IntRange(min=1), @@ -66,6 +82,12 @@ def write_run_to_file(environment: Environment, run_id: int): default=False, help="Use this option to include all test cases in this test run." ) +@click.option( + "--auto-close-run", + is_flag=True, + default=False, + help="Use this option to automatically close the created run." +) @click.option( "--run-case-ids", metavar="", diff --git a/trcli/data_providers/api_data_provider.py b/trcli/data_providers/api_data_provider.py index 05015106..3b98c144 100644 --- a/trcli/data_providers/api_data_provider.py +++ b/trcli/data_providers/api_data_provider.py @@ -1,6 +1,7 @@ from beartype.typing import List, Dict, Optional from serde.json import to_dict +from datetime import datetime, timezone from trcli.constants import OLD_SYSTEM_NAME_AUTOMATION_ID, UPDATED_SYSTEM_NAME_AUTOMATION_ID from trcli.data_classes.dataclass_testrail import TestRailSuite @@ -66,6 +67,8 @@ def add_run( self, run_name: Optional[str], case_ids=None, + start_date=None, + end_date=None, milestone_id=None, assigned_to_id=None, include_all=None, @@ -93,6 +96,18 @@ def add_run( "milestone_id": milestone_id, "case_ids": case_ids } + if isinstance(start_date, list) and start_date is not None: + try: + dt = datetime(start_date[2], start_date[0], start_date[1], tzinfo=timezone.utc) + body["start_on"] = int(dt.timestamp()) + except ValueError: + body["start_on"] = None + if isinstance(end_date, list) and end_date is not None: + try: + dt = datetime(end_date[2], end_date[0], end_date[1], tzinfo=timezone.utc) + body["due_on"] = int(dt.timestamp()) + except ValueError: + body["due_on"] = None if include_all is not None: body["include_all"] = include_all if assigned_to_id is not None: