Skip to content

Commit 3e3377d

Browse files
committed
Set test cases with untriaged and suitable automation coverage to null coverage
1 parent 92f268d commit 3e3377d

File tree

1 file changed

+249
-0
lines changed

1 file changed

+249
-0
lines changed
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
import logging
2+
import os
3+
import time
4+
5+
from dotenv import load_dotenv
6+
7+
from modules.testrail_integration import testrail_init
8+
9+
# Set up logging
10+
logging.basicConfig(
11+
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
12+
)
13+
14+
# Load env file from project root
15+
script_dir = os.path.dirname(__file__)
16+
project_root = os.path.abspath(os.path.join(script_dir, ".."))
17+
env_file_path = os.path.join(project_root, "testrail_credentials.env")
18+
load_dotenv(dotenv_path=env_file_path)
19+
20+
# TestRail project ID (Fx Desktop)
21+
PROJECT_ID = 17
22+
23+
# Specific suites to process
24+
SUITES = [
25+
("18215", "Address Bar and Search"),
26+
("1731", "Audio/Video"),
27+
("2525", "Bookmarks and History"),
28+
("29219", "Downloads"),
29+
("5259", "Drag and Drop"),
30+
("2085", "Find Toolbar"),
31+
("2054", "Form Autofill"),
32+
("498", "Geolocation"),
33+
("22801", "Language Packs"),
34+
("85", "Menus"),
35+
("6066", "Networking"),
36+
("1907", "Notifications"),
37+
("65", "PDF Viewer"),
38+
("43517", "Password Manager"),
39+
("5403", "Pocket"),
40+
("2241", "Preferences"),
41+
("2119", "Profile"),
42+
("73", "Printing UI"),
43+
("2126", "Reader View"),
44+
("102", "Scrolling"),
45+
("5833", "Security and Privacy"),
46+
("2130", "Sync & Firefox Account"),
47+
("2103", "Tabs"),
48+
("1997", "Theme and Toolbar"),
49+
]
50+
51+
# Automation status values
52+
AUTOMATION_STATUS = {"UNTRIAGED": 1, "SUITABLE": 2, "NOT_SUITABLE": 3, "COMPLETED": 4}
53+
54+
# Automation coverage values
55+
AUTOMATION_COVERAGE = {"NONE": 1, "PARTIAL": 2, "FULL": 3}
56+
57+
# Coverage value to name mapping for better logging
58+
COVERAGE_NAMES = {1: "None", 2: "Partial", 3: "Full"}
59+
60+
61+
def get_all_test_cases(tr, project_id, suite_id):
62+
"""Fetch all test cases from a suite by handling pagination."""
63+
all_cases = []
64+
offset = 0
65+
limit = 240 # Default limit for TestRail API is 250
66+
67+
while True:
68+
# Build endpoint with pagination parameters
69+
endpoint = (
70+
f"get_cases/{project_id}&suite_id={suite_id}&limit={limit}&offset={offset}"
71+
)
72+
73+
response = tr.client.send_get(endpoint)
74+
cases = response.get("cases", [])
75+
if not cases:
76+
break
77+
all_cases.extend(cases)
78+
# If the number of cases returned is less than the limit, we've reached the last page.
79+
if len(cases) < limit:
80+
break
81+
offset += limit
82+
83+
return all_cases
84+
85+
86+
def set_untriaged_suitable_to_null_coverage(
87+
tr, project_id, dry_run=True, batch_size=25
88+
):
89+
"""
90+
Set automation coverage to None for test cases that have automation status of Untriaged or Suitable
91+
"""
92+
start_time = time.time()
93+
try:
94+
# Track statistics
95+
total_cases_to_update = 0
96+
skipped_cases = 0
97+
updated_count = 0
98+
changed_case_ids = [] # Track all case IDs that will be changed
99+
100+
# Process each specified suite
101+
total_suites = len(SUITES)
102+
for index, (suite_id, suite_name) in enumerate(SUITES, 1):
103+
# Show progress
104+
logging.info(f"Processing suite {index}/{total_suites}: {suite_name}")
105+
106+
try:
107+
cases = get_all_test_cases(tr, project_id, suite_id)
108+
109+
# Filter cases that need updating
110+
update_targets = []
111+
for case in cases:
112+
status = case.get("custom_automation_status")
113+
coverage = case.get("custom_automation_coverage")
114+
115+
# Check if status is Untriaged or Suitable
116+
if status in [
117+
AUTOMATION_STATUS["UNTRIAGED"],
118+
AUTOMATION_STATUS["SUITABLE"],
119+
]:
120+
# Check coverage value
121+
if coverage in [
122+
AUTOMATION_COVERAGE["FULL"],
123+
AUTOMATION_COVERAGE["PARTIAL"],
124+
]:
125+
update_targets.append(case)
126+
elif coverage == AUTOMATION_COVERAGE["NONE"]:
127+
skipped_cases += 1
128+
129+
suite_update_count = len(update_targets)
130+
total_cases_to_update += suite_update_count
131+
132+
if suite_update_count > 0:
133+
logging.info(
134+
f"Found {suite_update_count} cases to update in '{suite_name}'"
135+
)
136+
137+
# Update in batches
138+
for i in range(0, len(update_targets), batch_size):
139+
batch = update_targets[i : i + batch_size]
140+
141+
if not dry_run:
142+
# Process batch updates
143+
batch_ids = []
144+
for case in batch:
145+
case_id = case["id"]
146+
coverage = case.get("custom_automation_coverage")
147+
coverage_name = COVERAGE_NAMES.get(coverage)
148+
149+
if coverage_name is None:
150+
logging.warning(
151+
f"Case {case_id} has unexpected coverage value: {coverage}"
152+
)
153+
coverage_name = "Unknown"
154+
155+
try:
156+
tr.update_case_field(
157+
case_id,
158+
"custom_automation_coverage",
159+
str(AUTOMATION_COVERAGE["NONE"]),
160+
)
161+
batch_ids.append(case_id)
162+
changed_case_ids.append((case_id, suite_name))
163+
updated_count += 1
164+
except Exception as e:
165+
logging.error(f"Error updating case {case_id}: {e}")
166+
167+
if batch_ids:
168+
logging.info(
169+
f"Updated batch of {len(batch_ids)} cases to None coverage"
170+
)
171+
else:
172+
# Log all cases in dry run mode
173+
logging.info(
174+
f"Would update batch of {len(batch)} cases to None coverage"
175+
)
176+
for case in batch:
177+
case_id = case["id"]
178+
coverage = case.get("custom_automation_coverage")
179+
coverage_name = COVERAGE_NAMES.get(coverage)
180+
181+
if coverage_name is None:
182+
logging.warning(
183+
f"Case {case_id} has unexpected coverage value: {coverage}"
184+
)
185+
coverage_name = "Unknown"
186+
187+
logging.info(
188+
f" Case {case_id} - {coverage_name} → None"
189+
)
190+
changed_case_ids.append((case_id, suite_name))
191+
192+
except Exception as e:
193+
logging.error(f"Error processing suite {suite_id}: {e}")
194+
continue
195+
196+
# Calculate execution time
197+
execution_time = time.time() - start_time
198+
199+
# Log summary
200+
logging.info("\n=== EXECUTION SUMMARY ===")
201+
logging.info(f"Total cases to update: {total_cases_to_update}")
202+
logging.info(f"Cases skipped (already None): {skipped_cases}")
203+
204+
if not dry_run:
205+
logging.info(f"Cases successfully updated: {updated_count}")
206+
else:
207+
logging.info(
208+
f"Cases that would be updated (dry run): {total_cases_to_update}"
209+
)
210+
211+
logging.info(f"Execution time: {execution_time:.2f} seconds")
212+
if total_suites > 0:
213+
logging.info(
214+
f"Average time per suite: {execution_time / total_suites:.2f} seconds"
215+
)
216+
if updated_count > 0:
217+
logging.info(
218+
f"Average time per update: {execution_time / updated_count:.2f} seconds"
219+
)
220+
221+
except Exception as e:
222+
logging.error(f"Error in overall process: {e}")
223+
224+
225+
def main():
226+
# Read credentials from environment
227+
base_url = os.environ.get("TESTRAIL_BASE_URL")
228+
username = os.environ.get("TESTRAIL_USERNAME")
229+
api_key = os.environ.get("TESTRAIL_API_KEY")
230+
231+
if not all([base_url, username, api_key]):
232+
logging.error("Missing TestRail credentials. Check your .env file.")
233+
return
234+
235+
logging.info(f"Loaded credentials for user: {username}")
236+
logging.info(f"Base URL: {base_url}")
237+
238+
tr = testrail_init()
239+
240+
# Safe approach to not accidentally update cases
241+
dry_run = True
242+
243+
# Process all cases in the project
244+
logging.info(f"Processing project ID: {PROJECT_ID}...")
245+
set_untriaged_suitable_to_null_coverage(tr, PROJECT_ID, dry_run)
246+
247+
248+
if __name__ == "__main__":
249+
main()

0 commit comments

Comments
 (0)