Skip to content

Commit d651795

Browse files
committed
Added Zephyr Squad Server support
This commit introduces initial support for the Zephyr Squad (server) variant. Only a number of the available Zephyr API calls are introduced by this commit. All testing for this commit was done on a self-hosted Zephyr Squad instance.
1 parent 35c37fe commit d651795

File tree

12 files changed

+570
-1
lines changed

12 files changed

+570
-1
lines changed
File renamed without changes.

examples/squad-server.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"""
2+
Usage examples of Zephyr Squad Server API wrappers.
3+
"""
4+
import logging
5+
6+
from zephyr import ZephyrSquad
7+
8+
# Enable logging with level Debug for more verbosity
9+
logging.basicConfig(level=logging.DEBUG)
10+
11+
12+
# Specify your Jira context to operate with:
13+
base_url = "https://jira.hosted.com/"
14+
15+
# Use the Jira certificate for TLS connections
16+
session_params = {
17+
"verify": "<path-to-certificate>"
18+
}
19+
20+
# Create an instance of Zephyr Squad
21+
zsquad = ZephyrSquad(
22+
base_url=base_url,
23+
token="<your_token>",
24+
session_attrs=session_params
25+
)
26+
27+
# Now we can start playing with the Zephyr API!
28+
29+
# Obtain a project's information
30+
project_info = zsquad.api.project.get_project_info("<your_project_key>")
31+
32+
# Obtain a project's versions/releases
33+
project_versions = zsquad.api.project.get_project_versions_by_id("<your_project_id>")
34+
35+
# Get the data of a testcase
36+
test_case = zsquad.api.test_cases.get_test_case("<your_case_key>", fields="id")
37+
38+
# Get the test steps from a testcase
39+
test_steps = zsquad.api.test_cases.get_test_steps("<your_step_id>")
40+
41+
# Get the list of all test cycles for a specific release
42+
test_cycle = zsquad.api.test_cycles.get_test_cycle(project_id="<your_project_id>", version_id="<your_version_id>")
43+
44+
# Get all folders from a test cycle
45+
test_cycle_folders = zsquad.api.folder.get_folder(cycle_id="<your_cycle_id>", project_id="<your_project_id>", version_id="<your_version_id>")
46+
47+
# Get all test executions from a test case
48+
test_executions = zsquad.api.test_executions.get_test_execution(test_id_or_key="<your_case_id_or_key>")
49+
50+
# Create a new test case for a project
51+
data = {
52+
"fields": {
53+
"assignee": {
54+
"name": "<jira_username>"
55+
},
56+
"description": "<your_case_description>"
57+
}
58+
}
59+
ret_data = zsquad.api.test_cases.create_test_case(project_id="<your_project_id>", summary="<your_case_summary>", data=data)
60+
61+
# Execute ZQL search query
62+
demo_query = "project = '<your_project_id>' AND cycleName = '<your_cycle_name>'"
63+
zql_search_res = zsquad.api.utils.run_zql_search(query=demo_query, maxRecords=200)
64+
65+
# Create a new test cycle for a project based on an existing test case
66+
data = {
67+
"clonedCycleId": "<your_cycle_id>",
68+
"description": "<your_cycle_description>",
69+
"build": "",
70+
"startDate": "29/Nov/22",
71+
"endDate": "4/Dec/22",
72+
"environment": ""
73+
}
74+
ret_data = zsquad.api.test_cycles.create_test_cycle(project_id="<your_project_id>", version_id="<your_version_id>", cycle_name="<your_cycle_name>", data=data)
75+
76+
# Create a new test folder for a test cycle
77+
data = {
78+
"cycleId": 1508, # it will be rewritten by the function
79+
"name": "<your_folder_name>",
80+
"description": "<your_folder_description>",
81+
"projectId": 10600, # it will be rewritten by the function
82+
"versionId": -1, # it will be rewritten by the function
83+
"clonedFolderId": -1
84+
}
85+
ret_data = zsquad.api.folder.create_folder(project_id="<your_project_id>", version_id="<your_version_id>", cycle_id="<your_cycle_id>", data=data)
86+
87+
# Add a new test case for a test cycle
88+
data = {
89+
"issues":["<your_case_key>"],
90+
}
91+
ret_data = zsquad.api.test_cases.add_test_to_cycle(project_id="<your_project_id>", cycle_id="<your_cycle_id>", method="1", data=data)
92+
93+
# Obtain the execution details
94+
exec_details = zsquad.api.test_executions.get_execution_properties(exec_id="<execution_id>")
95+
# print(exec_details)
96+
97+
# Obtain the execution steps from an execution
98+
exec_steps = zsquad.api.test_executions.get_test_steps(exec_id="<execution_id>")
99+
100+
# Update the status of an execution step
101+
step_status = zsquad.api.test_executions.update_test_step_status(step_id="<execution_step_id>", status=2)
102+
103+
# Update the status of an execution step
104+
exec_status = zsquad.api.test_executions.update_execution_status(exec_id="<execution_id>", status=2)
105+
106+
# Update a folder name and description
107+
data = {
108+
"description": "<new_folder_decription>"
109+
}
110+
ret_data = zsquad.api.folder.update_folder(project_id="<your_project_id>", version_id="<your_version_id>", cycle_id="<your_cycle_id>", folder_id="<your_folder_id>", name="<your_new_folder_name>")
111+
112+
# Delete 3 test executions
113+
delete_status = zsquad.api.test_executions.delete_bulk_execution(execution_id=["<your_exec_id_1>", "<your_exec_id_2>", "<your_exec_id_3>"])

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = zephyr-python-api
3-
version = 0.0.3
3+
version = 0.0.4
44
author = Petr Sharapenko
55
author_email = [email protected]
66
description = Zephyr (TM4J) Python REST API wrapper

zephyr/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
from zephyr.scale import API_V1, API_V2, ZephyrScale
2+
from zephyr.squad import API_V1, ZephyrSquad
23
from zephyr.utils.common import cookie_str_to_dict

zephyr/squad/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from zephyr.squad.squad import API_V1, ZephyrSquad

zephyr/squad/server/__init__.py

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from .endpoints import (ProjectEndpoints,
2+
TestCaseEndpoints,
3+
TestCyclesEndpoints,
4+
FolderEndpoints,
5+
TestExecutionEndpoints,
6+
TestUtilsEndpoints)
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
from copy import deepcopy
2+
from ...zephyr_session import ZephyrSession
3+
from .paths import ServerPaths as Paths
4+
5+
6+
class EndpointTemplate:
7+
"""Class with basic constructor for endpoint classes"""
8+
def __init__(self, session: ZephyrSession):
9+
self.session = session
10+
11+
def _dict_merge(self, a, b):
12+
"""Recursively merges 2 dictionaries and return the merged dictionary"""
13+
result = deepcopy(a)
14+
for k, v in b.items():
15+
if k in result and isinstance(result[k], dict):
16+
result[k] = self._dict_merge(result[k], v)
17+
else:
18+
result[k] = deepcopy(v)
19+
return result
20+
21+
class TestCaseEndpoints(EndpointTemplate):
22+
"""Api wrapper for "Test Case" endpoints"""
23+
24+
def get_test_case(self, test_case_key, **params):
25+
"""Retrieve the Test Case matching the given key"""
26+
return self.session.get(Paths.CASE_KEY.format(test_case_key),
27+
params=params)
28+
29+
def get_test_steps(self, test_case_id):
30+
"""Retrieve the Test Case matching the given key"""
31+
return self.session.get(Paths.CASE_KEY_STEPS.format(test_case_id))
32+
33+
def _get_test_case_type(self):
34+
"""Retrieve the Test Case matching the given key"""
35+
case_type = self.session.get(Paths.CASE_ISSUE_TYPE)
36+
return case_type["testcaseIssueTypeId"]
37+
38+
def create_test_case(self, project_id, summary, data):
39+
"""
40+
Creates a new Test Case based on a minimum required fields.
41+
(https://support.smartbear.com/zephyr-squad-server/docs/api/how-to/create-tests.html)
42+
See Zephyr Squad Test Case creation documentation to better understand what can be
43+
modified.
44+
"""
45+
case_type = self._get_test_case_type()
46+
json = {
47+
"fields": {
48+
"project": {
49+
"id": project_id
50+
},
51+
'issuetype': {
52+
'id': case_type
53+
},
54+
"summary": summary,
55+
}
56+
}
57+
merged_json = self._dict_merge(data, json)
58+
return self.session.post(Paths.CASE, json=merged_json)
59+
60+
def add_test_to_cycle(self, project_id, cycle_id, method, data):
61+
"""
62+
Add an existing Test Case to a Test Cycle based on a minimum required fields.
63+
(https://support.smartbear.com/zephyr-squad-server/docs/api/how-to/add-tests-to-cycle.html)
64+
See Zephyr Squad documentation to better understand what can be modified.
65+
"""
66+
json = {
67+
'cycleId': cycle_id,
68+
'projectId': project_id,
69+
'method': method
70+
}
71+
merged_json = self._dict_merge(data, json)
72+
return self.session.post(Paths.CASE_TO_CYCLE, json=merged_json)
73+
74+
75+
class TestUtilsEndpoints(EndpointTemplate):
76+
"""Api wrapper for different helpers endpoints like ZQL search"""
77+
78+
def run_zql_search(self, query, **params):
79+
"""Retrieve the Test Cycle matching the given key"""
80+
params.update(zqlQuery=f'({query})')
81+
return self.session.get(Paths.ZQL_SEARCH, params=params)
82+
83+
84+
class TestCyclesEndpoints(EndpointTemplate):
85+
"""Api wrapper for "Test Cycles" endpoints"""
86+
87+
def get_test_cycle(self, project_id, version_id, **params):
88+
"""Retrieve the Test Cycle matching the given key"""
89+
params.update(projectId=project_id, versionId=version_id)
90+
return self.session.get(Paths.CYCLE, params=params)
91+
92+
def create_test_cycle(self, project_id, version_id, cycle_name, data):
93+
"""
94+
Creates a new Test Cycle based on a minimum required fields.
95+
(https://support.smartbear.com/zephyr-squad-server/docs/api/how-to/create-cycle.html)
96+
See Zephyr Squad Test Cycle creation documentation to better understand what can be
97+
modified.
98+
"""
99+
json = {
100+
'name': cycle_name,
101+
'projectId': project_id,
102+
'versionId': version_id
103+
}
104+
merged_json = self._dict_merge(data, json)
105+
return self.session.post(Paths.CYCLE, json=merged_json)
106+
107+
108+
class TestExecutionEndpoints(EndpointTemplate):
109+
"""Api wrapper for "Test Execution" endpoints"""
110+
111+
def get_test_execution(self, test_id_or_key, **params):
112+
"""Retrieve all execution of a test case by the issue id or issue key"""
113+
params.update(testIdOrKey=test_id_or_key)
114+
return self.session.get(Paths.EXEC_BY_TEST, params=params)
115+
116+
def get_execution_properties(self, exec_id, **params):
117+
"""Retrieve all execution details"""
118+
return self.session.get(Paths.EXEC_PROP.format(exec_id),
119+
params=params)
120+
121+
def get_test_steps(self, exec_id, **params):
122+
"""Retrieve all execution steps"""
123+
params.update(executionId=exec_id)
124+
params.update(expand="")
125+
return self.session.get(Paths.STEP, params=params)
126+
127+
def update_test_step_status(self, step_id, status):
128+
"""
129+
Updates a Test Step Status. Available status values:
130+
-1 = UNEXECUTED | 1 = PASS | 2 = FAIL | 3 = WIP | 4 = BLOCKED
131+
"""
132+
json = {
133+
"status": status
134+
}
135+
return self.session.put(Paths.STEP_ID.format(step_id), json=json)
136+
137+
def update_execution_status(self, exec_id, status):
138+
"""
139+
Updates an Test Execution Status. Available status values:
140+
-1 = UNEXECUTED | 1 = PASS | 2 = FAIL | 3 = WIP | 4 = BLOCKED
141+
"""
142+
json = {
143+
"status": status
144+
}
145+
return self.session.put(Paths.EXEC.format(exec_id), json=json)
146+
147+
def delete_bulk_execution(self, execution_id):
148+
"""Delete bulk Execution by Execution Id"""
149+
if not isinstance(execution_id, list):
150+
execution_id = [ execution_id ]
151+
152+
json = {
153+
"executions": execution_id
154+
}
155+
return self.session.delete(Paths.EXEC_DEL, json=json)
156+
157+
158+
class FolderEndpoints(EndpointTemplate):
159+
"""Api wrapper for "Folder" endpoints"""
160+
161+
def get_folder(self, project_id, version_id, cycle_id, **params):
162+
"""Retrieve the Folders from a Test Cycle"""
163+
params.update(projectId=project_id, versionId=version_id)
164+
return self.session.get(Paths.FOLDER.format(cycle_id), params=params)
165+
166+
def create_folder(self, project_id, version_id, cycle_id, data):
167+
"""
168+
Create a Folder in a Test Cycle based on a minimum required fields.
169+
(https://support.smartbear.com/zephyr-squad-server/docs/api/how-to/create-folder-in-cycle.html)
170+
See Zephyr Squad Folder creation documentation to better understand what can be
171+
modified.
172+
"""
173+
json = {
174+
'cycleId': cycle_id,
175+
'projectId': project_id,
176+
'versionId': version_id
177+
}
178+
merged_json = self._dict_merge(data, json)
179+
return self.session.post(Paths.FOLDER_CREATE, json=merged_json)
180+
181+
def update_folder(self, project_id, cycle_id, version_id, folder_id, name, data=dict()):
182+
"""
183+
Updates a Test Folder information.
184+
(https://zephyrsquadserver.docs.apiary.io/#reference/folderresource/update-a-folder-information/update-a-folder-information)
185+
"""
186+
json = {
187+
'cycleId': cycle_id,
188+
'projectId': project_id,
189+
'versionId': version_id,
190+
"folderId": folder_id,
191+
"name": name,
192+
}
193+
merged_json = self._dict_merge(data, json)
194+
return self.session.put(Paths.FOLDER_ID.format(folder_id), json=merged_json)
195+
196+
197+
class ProjectEndpoints(EndpointTemplate):
198+
"""Api wrapper for "Project" endpoints"""
199+
200+
def get_project_info(self, project_key):
201+
"""
202+
Retrieve project information
203+
:params project_key: project key (Ex: DEMOPROJ)
204+
:returns: dictionary containing the project information
205+
"""
206+
return self.session.get(Paths.PRJ_ID.format(project_key))
207+
208+
def get_project_versions_by_key(self, project_key):
209+
"""
210+
Retrieve all project versions (releases) by project key using Jira API
211+
:params project_key: project key (Ex: DEMOPROJ)
212+
:returns: dictionary containing the project information
213+
"""
214+
return self.session.get(Paths.PRJ_VERSIONS_BY_KEY.format(project_key))
215+
216+
def get_project_versions_by_id(self, project_id, **params):
217+
"""
218+
Retrieve all project versions (releases) by project id using Zephyr API
219+
:params project_key: project id (Ex: 77303)
220+
:returns: dictionary containing the project information
221+
"""
222+
params.update(projectId=project_id)
223+
return self.session.get(Paths.PRJ_VERSIONS_BY_ID.format(project_id),
224+
params=params)

0 commit comments

Comments
 (0)