Join 34,000+ subscribers and receive articles from our blog about software quality, testing, QA and security.

I created a script to combine test plans together

Hello all,

Sometimes we need to create larger test plans composed of several smaller test plans. I came up with a way to programmatically combine TestRail test plans so that I just need to pass in test plan ids and then it publishes a new test plan that contains all of the test cases in the input test plans.

The script is ‘dumb’ in that it doesn’t maintain any configurations, but it is smart in that it doesn’t duplicate test cases if one or more of the input test plans share test cases.

from collections import defaultdict
import testrail

def get_suite_tests_from_test_plans(test_plan_ids:list): # returns a dict of suties and cases in that suite
    suite_cases = defaultdict(list) # I am using a default dict to avoid key errors 
    all_case_ids = [] # Need to keep track of all test cases to make sure there is not duplication

    for test_plan_id in test_plan_ids:
        test_plan_dict = client_obj.send_get(uri=f"get_plan/{test_plan_id}")
        for entry in test_plan_dict["entries"]:
            for run in entry["runs"]:
                run_id = run["id"]
                tests = client_obj.send_get(uri=f"get_tests/{run_id}")
                case_ids = [test["case_id"] for test in tests if test["case_id"] not in all_case_ids] # Filtering the duplicates out
                if case_ids:
                    all_case_ids += case_ids
                    suite_cases[str(run["suite_id"])] = case_ids

    return suite_cases

def create_test_plan(project_id, suite_cases):

    entries = []
    for suite_id, case_list in suite_cases.items():

        new_entry = {
            "suite_id": suite_id,
            "assignedto_id": None,           # Default assignee
            "include_all": False,          # Default selection
            "case_ids": case_list


    new_test_plan_dict = {"name": "Combined Test Plan", "entries": entries}
    return client_obj.send_post(f"add_plan/{project_id}", new_test_plan_dict)

if __name__ == "__main__":
    client_obj = testrail.APIClient("client_url")
    client_obj.user = "username/email"
    client_obj.password = "password"
    PROJECT_ID = "9"

    TEST_PLAN_1_ID = "3854"
    TEST_PLAN_2_ID = "3822"
    TEST_PLAN_3_ID = "3834"
    TEST_PLAN_4_ID = "3830"

    SUITE_CASES = get_suite_tests_from_test_plans([TEST_PLAN_1_ID, TEST_PLAN_2_ID, TEST_PLAN_3_ID, TEST_PLAN_4_ID])
    NEW_TEST_PLAN = create_test_plan(PROJECT_ID, SUITE_CASES)