Skip to content

Commit 5b91e12

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 5b91e12

34 files changed

+1300
-45
lines changed

README.md

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,33 @@
44
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/zephyr-python-api)
55
![PyPI](https://img.shields.io/pypi/v/zephyr-python-api)
66
![PyPI - License](https://img.shields.io/pypi/l/zephyr-python-api)
7-
### Project description
8-
This is a set of wrappers for Zephyr Scale (TM4J) REST API. This means you can interact with Zephyr Scale without GUI, access it with python code and create automation scripts for your every day interactions.
7+
## Project description
8+
This is a set of wrappers for both Zephyr Scale and Zephyr Squad (TM4J) REST APIs. This means you can interact with Zephyr without GUI, access it with python code and create automation scripts for your every day interactions.
99

1010
To be done:
1111
* More usage examples
1212
* Tests, tests and tests for gods of testing
1313
* Convenient docs
1414
* Implementing higher level wrappers representing Test Case, Test Cycle, etc.
1515

16-
### Installation
16+
## Installation
1717

1818
```
1919
pip install zephyr-python-api
2020
```
2121

22-
### Example usage
22+
## Example usage
2323

24-
Zephyr Cloud auth:
24+
### Zephyr Scale
25+
26+
Zephyr Scale Cloud auth:
2527
```python
2628
from zephyr import ZephyrScale
2729

2830
zscale = ZephyrScale(token=<your_token>)
2931
```
3032

31-
Zephyr Server (TM4J) auth:
33+
Zephyr Scale Server (TM4J) auth:
3234
```python
3335
from zephyr import ZephyrScale
3436

@@ -58,17 +60,62 @@ test_case = zapi.test_cases.get_test_case("<test_case_id>")
5860
creation_result = zapi.test_cases.create_test_case("<project_key>", "test_case_name")
5961
```
6062

61-
### Troubleshooting
63+
### Zephyr Squad
64+
65+
Zephyr Squad Server (TM4J) auth:
66+
```python
67+
from zephyr import ZephyrSquad
68+
69+
# Auth can be made with Jira token
70+
auth = {"token": "<your_jira_token>"}
71+
72+
# or with login and password (suggest using get_pass)
73+
auth = {"username": "<your_login>", "password": "<your_password>"}
74+
75+
# or even session cookie dict
76+
auth = {"cookies": "<session_cookie_dict>"}
77+
78+
zsquad = ZephyrSquad(base_url=base_url, **auth)
79+
```
80+
81+
Then it is possible to interact with api wrappers:
82+
```python
83+
# Obtain a project's information
84+
project_info = zsquad.actions.project.get_project_info("<project_key>")
85+
86+
# Obtain a project's versions/releases
87+
project_versions = zsquad.api.util_resource.get_all_versions("<project_id>")
88+
89+
# Get a single test case by its id
90+
test_case = zsquad.actions.test_cases.get_test_case("<case_key>", fields="id")
91+
92+
# Create a new test case for a project
93+
data = {
94+
"fields": {
95+
"assignee": {
96+
"name": "<jira_username>"
97+
},
98+
"description": "<case_description>"
99+
}
100+
}
101+
creation_result = zsquad.actions.test_cases.create_test_case(projectId="<project_id>", summary="<case_summary>", data=data)
102+
```
103+
104+
## Troubleshooting
62105

63106
For troubleshooting see [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
64107

65108

66-
### License
109+
## License
67110

68111
This library is licensed under the Apache 2.0 License.
69112

70-
### Links
113+
## Links
71114

72115
[Zephyr Scale Cloud API docs](https://support.smartbear.com/zephyr-scale-cloud/api-docs/)
73116

74-
[Zephyr Scale Server API docs](https://support.smartbear.com/zephyr-scale-server/api-docs/v1/)
117+
[Zephyr Scale Server API docs](https://support.smartbear.com/zephyr-scale-server/api-docs/v1/)
118+
119+
[Zephyr Squad Server API docs](https://zephyrsquadserver.docs.apiary.io/)
120+
121+
[Zephyr Squad Server How to API docs](https://support.smartbear.com/zephyr-squad-server/docs/api/index.html)
File renamed without changes.

examples/squad-server.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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="<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.actions.project.get_project_info("<project_key>")
31+
32+
# Obtain a project's versions/releases
33+
project_versions = zsquad.api.util_resource.get_all_versions("<project_id>")
34+
35+
# Get the data of a testcase
36+
test_case = zsquad.actions.test_cases.get_test_case("<case_key>", fields="id")
37+
38+
# Get the test steps from a testcase
39+
test_steps = zsquad.api.teststep_resource.get_list_of_teststeps("<issue_id>")
40+
41+
# Get the information about a test cycle
42+
test_cycle = zsquad.api.cycle_resource.get_cycle_information(id="<cycle_id>")
43+
44+
# Get the list of all test cycles for a specific release
45+
test_cycles = zsquad.api.cycle_resource.get_list_of_cycle(projectId="<project_id>", versionId="<version_id>")
46+
47+
# Get all folders from a test cycle
48+
test_cycle_folders = zsquad.api.cycle_resource.get_the_list_of_folder_for_a_cycle(cycleId="<cycle_id>", projectId="<project_id>", versionId="<version_id>")
49+
50+
# Get all test executions from a test case
51+
test_executions = zsquad.api.traceability_resource.get_list_of_search_execution_by_test(testIdOrKey="<case_id_or_key>")
52+
53+
# Create a new test case for a project
54+
data = {
55+
"fields": {
56+
"assignee": {
57+
"name": "<jira_username>"
58+
},
59+
"description": "<case_description>"
60+
}
61+
}
62+
ret_data = zsquad.actions.test_cases.create_test_case(projectId="<project_id>", summary="<case_summary>", data=data)
63+
64+
# Execute ZQL search query
65+
demo_query = "project = '<project_id>' AND cycleName = '<cycle_name>'"
66+
zql_search_res = zsquad.api.execution_search_resource.execute_search_to_get_search_result(query=demo_query, maxRecords=200)
67+
68+
# Create a new test cycle for a project based on an existing test case
69+
data = {
70+
"clonedCycleId": "<cycle_id>",
71+
"description": "<cycle_description>",
72+
"build": "",
73+
"startDate": "29/Nov/22",
74+
"endDate": "4/Dec/22",
75+
"environment": ""
76+
}
77+
ret_data = zsquad.api.cycle_resource.create_new_cycle(projectId="<project_id>", versionId="<version_id>", name="<cycle_name>", data=data)
78+
79+
# Create a new test folder for a test cycle
80+
data = {
81+
"cycleId": 1508, # it will be rewritten by the function
82+
"name": "<folder_name>",
83+
"description": "<folder_description>",
84+
"projectId": 10600, # it will be rewritten by the function
85+
"versionId": -1, # it will be rewritten by the function
86+
"clonedFolderId": -1
87+
}
88+
ret_data = zsquad.api.folder_resource.create_folder_under_cycle(projectId="<project_id>", versionId="<version_id>", cycleId="<cycle_id>", data=data)
89+
90+
# Add a new test case for a test cycle
91+
data = {
92+
"issues":["<case_key>"],
93+
}
94+
ret_data = zsquad.api.execution_resource.add_test_to_cycle(projectId="<project_id>", cycleId="<cycle_id>", method="1", data=data)
95+
96+
# Obtain the execution details
97+
exec_details = zsquad.api.execution_resource.get_execution_information(executionId="<execution_id>")
98+
99+
# Obtain the execution steps from an execution
100+
exec_steps = zsquad.api.step_result_resource.get_list_of_step_result(executionId="<execution_id>")
101+
102+
# Update the status of an execution step
103+
step_status = zsquad.api.step_result_resource.update_step_result_information(stepResultId="<execution_step_id>", status=2)
104+
105+
# Update the execution status
106+
exec_status = zsquad.api.execution_resource.update_execution_details(executionId="<execution_id>", status=2)
107+
108+
# Update a folder name and description
109+
data = {
110+
"description": "<new_folder_decription>"
111+
}
112+
ret_data = zsquad.api.folder_resource.update_folder_information(projectId="<project_id>", versionId="<version_id>", cycleId="<cycle_id>", folderId="<folder_id>", name="<new_folder_name>")
113+
114+
# Delete 3 test executions
115+
delete_status = zsquad.api.execution_resource.delete_bulk_execution(executionId=["<exec_id_1>", "<exec_id_2>", "<exec_id_3>"])
116+
117+
# Show the progress of a job
118+
job_status = zsquad.api.execution_resource.get_job_progress_status(jobProgressToken="<job_progress_token>")
119+
120+
# Get a test step's detailed information
121+
test_step = zsquad.api.teststep_resource.get_teststep_information(testStepId="<test_step_id>", issueId="<issue_id>")
122+
123+
# Add a attachment (for a execution result: entityId=executionId and entityType='Execution')
124+
attach = zsquad.api.attachment_resource.add_attachment_into_entity(filePath="<file_path>", entityId="<entity_id>", entityType='<entity_type>')
125+
126+
# Add a assignee to a execution result
127+
add_assignee = zsquad.api.execution_resource.add_assignee_to_execution(executionId="<exec_id>", assignee="<username>")

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 = nassauwinter@gmail.com
66
description = Zephyr (TM4J) Python REST API wrapper

tests/unit/test_zephyr_session.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from requests import Session
33

44
from zephyr.scale.scale import DEFAULT_BASE_URL, ZephyrSession
5-
from zephyr.scale.zephyr_session import INIT_SESSION_MSG, InvalidAuthData
5+
from zephyr.common.zephyr_session import INIT_SESSION_MSG, InvalidAuthData
66

77
REQUESTS_SESSION_PATH = "requests.sessions.Session"
88
GETLOGGER_PATH = "logging.getLogger"
@@ -20,7 +20,7 @@ def test_creation(self, mocker):
2020
assert zsession.base_url == DEFAULT_BASE_URL, (f"Attribute base_url expected to be {DEFAULT_BASE_URL}, "
2121
f"not {zsession.base_url}")
2222
assert isinstance(zsession._session, Session)
23-
logger_mock.assert_called_with("zephyr.scale.zephyr_session")
23+
logger_mock.assert_called_with("zephyr.common.zephyr_session")
2424

2525
def test_token_auth(self, mocker):
2626
"""Test token auth"""

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 ZephyrSquad
23
from zephyr.utils.common import cookie_str_to_dict

zephyr/common/endpoint_template.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .zephyr_session import ZephyrSession
2+
3+
4+
class EndpointTemplate:
5+
"""Class with basic constructor for endpoint classes"""
6+
def __init__(self, session: ZephyrSession):
7+
self.session = session

zephyr/scale/zephyr_session.py renamed to zephyr/common/zephyr_session.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class InvalidAuthData(Exception):
1313

1414
class ZephyrSession:
1515
"""
16-
Zephyr Scale basic session object.
16+
Zephyr basic session object.
1717
1818
:param base_url: url to make requests to
1919
:param token: auth token
@@ -83,22 +83,38 @@ def delete(self, endpoint: str, **kwargs):
8383
"""Delete request wrapper"""
8484
return self._request("delete", endpoint, **kwargs)
8585

86-
def get_paginated(self, endpoint, params=None):
86+
def get_paginated(self, endpoint, params=None, zscale=True):
8787
"""Get paginated data"""
8888
self.logger.debug(f"Get paginated data from endpoint={endpoint} and params={params}")
8989
if params is None:
9090
params = {}
9191

9292
while True:
9393
response = self.get(endpoint, params=params)
94-
if "values" not in response:
95-
return
96-
for value in response.get("values", []):
97-
yield value
98-
if response.get("isLast") is True:
99-
break
100-
params_str = urlparse(response.get("next")).query
101-
params.update(parse_qs(params_str))
94+
if zscale: # Zephyr Scale
95+
if "values" not in response:
96+
return
97+
for value in response.get("values", []):
98+
yield value
99+
if response.get("isLast") is True:
100+
break
101+
params_str = urlparse(response.get("next")).query
102+
params.update(parse_qs(params_str))
103+
104+
else: # Zephyr Squad
105+
if not response.get("executions"):
106+
return
107+
for execution in response.get("executions", []):
108+
yield execution
109+
if response.get("linksNew")[-1] == response.get("currentIndex") or \
110+
response.get("maxResultAllowed") + response.get("offset", 0) >= response.get("totalCount"):
111+
break
112+
113+
if params.get("offset"):
114+
new_offset = params['offset'] + response.get("maxResultAllowed")
115+
else:
116+
new_offset = response.get("maxResultAllowed")
117+
params.update(offset=new_offset)
102118
return
103119

104120
def post_file(self, endpoint: str, file_path: str, to_files=None, **kwargs):

zephyr/scale/cloud/cloud_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22

3-
from zephyr.scale.zephyr_session import ZephyrSession
3+
from zephyr.common.zephyr_session import ZephyrSession
44
from zephyr.scale.cloud.endpoints import (AutomationEndpoints,
55
EnvironmentEndpoints,
66
FolderEndpoints,

zephyr/scale/cloud/endpoints/automations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from json import dumps
22

3-
from ...zephyr_session import ZephyrSession
3+
from zephyr.common.zephyr_session import ZephyrSession
44

55

66
class AutomationEndpoints:

0 commit comments

Comments
 (0)