From 3a2c2b86417aa35a10b50aeca8fff0ff03a2aea6 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sat, 17 Jun 2023 22:56:22 +0200 Subject: [PATCH 01/37] Docs: Add sphinx and sphinx-rtd-theme to dev packages --- Pipfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Pipfile b/Pipfile index 76d1785..956bb2c 100644 --- a/Pipfile +++ b/Pipfile @@ -14,3 +14,5 @@ pylint = "*" build = "*" twine = "*" tox-gh-actions = "*" +sphinx = "*" +sphinx-rtd-theme = "*" From 9b6502ff2b7d809aa643f259b3d348d2f2a4d5f0 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sat, 17 Jun 2023 22:58:28 +0200 Subject: [PATCH 02/37] Docs: Add make files --- docs/Makefile | 20 ++++++++++++++++++++ docs/make.bat | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/make.bat diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..747ffb7 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd From 45382cb8c7777ee66e0a7f3e5775fca666513214 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sat, 17 Jun 2023 22:59:16 +0200 Subject: [PATCH 03/37] Docs: Initial add conf.py for sphinx --- docs/source/conf.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 docs/source/conf.py diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..5211cbc --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,33 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +import os +import sys +sys.path.insert(0, os.path.abspath('../../zephyr/')) + + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'Zephyr Python API' +copyright = '2023, Petr Sharapenko' +author = 'Petr Sharapenko' +release = '0.0.4' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = ['sphinx.ext.autodoc'] + +templates_path = ['_templates'] +exclude_patterns = [] + + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] From 69beb217d06eb9a348178c2b9e579050217f1ad5 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sat, 17 Jun 2023 22:59:46 +0200 Subject: [PATCH 04/37] Docs: Initial add .rst files --- docs/source/examples.rst | 40 ++++++++++++++++++++++++++++++++++++ docs/source/index.rst | 28 +++++++++++++++++++++++++ docs/source/installation.rst | 33 +++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 docs/source/examples.rst create mode 100644 docs/source/index.rst create mode 100644 docs/source/installation.rst diff --git a/docs/source/examples.rst b/docs/source/examples.rst new file mode 100644 index 0000000..6d5fd06 --- /dev/null +++ b/docs/source/examples.rst @@ -0,0 +1,40 @@ +Examples +============= + +Authentication +------------- + +Zephyr Cloud auth: + +.. code-block:: python + + from zephyr import ZephyrScale + zscale = ZephyrScale(token=) + +Zephyr Server/Data Center (TM4J) auth: + +.. code-block:: python + + from zephyr import ZephyrScale + # Auth can be made with Jira token + auth = {"token": ""} + # or with login and password (suggest using get_pass) + auth = {"username": "", "password": ""} + # or even session cookie dict + auth = {"cookies": ""} + zscale = ZephyrScale.server_api(base_url=, **auth) + +Usage +------------- + +Then it is possible to interact with api wrappers: + +.. code-block:: python + + zapi = zscale.api + # Get all test cases + all_test_cases = zapi.test_cases.get_test_cases() + # Get a single test case by its id + test_case = zapi.test_cases.get_test_case("") + # Create a test case + creation_result = zapi.test_cases.create_test_case("", "test_case_name") diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..e54f3e6 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,28 @@ +.. Zephyr Python API documentation master file, created by + sphinx-quickstart on Sat Jun 17 21:27:13 2023. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Zephyr Python API's documentation! +============================================= + +The package is a set of python wrappers for Zephyr Scale (TM4J) REST API (both Cloud and Server/DataCenter). Interact with Zephyr Scale without GUI, access it with python code and create automation scripts for your daily routines. + +.. toctree:: + :maxdepth: 3 + :caption: Contents: + + installation + examples + +Limitations +*********** + +The wrappers only implement public API methods from the official SmartBear Zephyr Scale Cloud and Server/DataCenter APIs. + +Useful links +*********** + +`Zephyr Scale Cloud API docs `_ + +`Zephyr Scale Server/Data Center API docs `_ diff --git a/docs/source/installation.rst b/docs/source/installation.rst new file mode 100644 index 0000000..e6273ff --- /dev/null +++ b/docs/source/installation.rst @@ -0,0 +1,33 @@ +Installation +============ + +From PyPI +*********** + +Simply use pip to install `the package from PYPI `_: + +.. code-block:: console + + pip install zephyr-python-api + +From source +*********** + +* Git clone repository + +.. code-block:: console + + git clone https://github.com/nassauwinter/zephyr-python-api.git + + +* Install dependencies: + +.. code:: console + + pip install -r requirements.txt + +* or better with `pipenv `_: + +.. code:: console + + pipenv install From e6d782bbd20a2c88877824c9d3c39bae99abf6c4 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sun, 18 Jun 2023 00:01:17 +0200 Subject: [PATCH 05/37] Docs: Add requirements.txt for installation from source --- requirements.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..61a9259 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +# +# These requirements were autogenerated by pipenv +# To regenerate from the project's Pipfile, run: +# +# pipenv lock --requirements +# + +-i https://pypi.org/simple +certifi==2023.5.7; python_version >= '3.6' +charset-normalizer==3.1.0; python_full_version >= '3.7.0' +idna==3.4; python_version >= '3.5' +requests==2.31.0 +urllib3==2.0.3; python_full_version >= '3.7.0' From 4f016f3e5ca6ba40fc2d514b2520b4f70be0c570 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sun, 18 Jun 2023 00:04:35 +0200 Subject: [PATCH 06/37] Docs: Add docs/requirements.txt for docs and .readthedocs.yaml --- .readthedocs.yaml | 23 ++++++++++++++ docs/requirements.txt | 74 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 .readthedocs.yaml create mode 100644 docs/requirements.txt diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..45f0b96 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,23 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.8" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + builder: html + configuration: docs/conf.py + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..7eff0db --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,74 @@ +# +# These requirements were autogenerated by pipenv +# To regenerate from the project's Pipfile, run: +# +# pipenv lock --requirements --dev-only +# + +-i https://pypi.org/simple +alabaster==0.7.13; python_version >= '3.6' +astroid==2.15.5; python_full_version >= '3.7.2' +babel==2.12.1; python_version >= '3.7' +bleach==6.0.0; python_version >= '3.7' +build==0.10.0 +cachetools==5.3.1; python_version >= '3.7' +certifi==2023.5.7; python_version >= '3.6' +chardet==5.1.0; python_version >= '3.7' +charset-normalizer==3.1.0; python_full_version >= '3.7.0' +colorama==0.4.6; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6' +dill==0.3.6; python_version < '3.11' +distlib==0.3.6 +docutils==0.18.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +exceptiongroup==1.1.1; python_version < '3.11' +filelock==3.12.2; python_version >= '3.7' +idna==3.4; python_version >= '3.5' +imagesize==1.4.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +importlib-metadata==6.6.0; python_version < '3.10' and python_version < '3.12' +iniconfig==2.0.0; python_version >= '3.7' +isort==5.12.0; python_version >= '3.8' +jaraco.classes==3.2.3; python_version >= '3.7' +jinja2==3.1.2; python_version >= '3.7' +keyring==23.13.1; python_version >= '3.7' +lazy-object-proxy==1.9.0; python_version >= '3.7' +markdown-it-py==3.0.0; python_version >= '3.8' +markupsafe==2.1.3; python_version >= '3.7' +mccabe==0.7.0; python_version >= '3.6' +mdurl==0.1.2; python_version >= '3.7' +more-itertools==9.1.0; python_version >= '3.7' +packaging==23.1; python_version >= '3.7' +pkginfo==1.9.6; python_version >= '3.6' +platformdirs==3.5.3; python_version >= '3.7' +pluggy==1.0.0; python_version >= '3.6' +pygments==2.15.1; python_version >= '3.7' +pylint==2.17.4 +pyproject-api==1.5.2; python_version >= '3.7' +pyproject-hooks==1.0.0; python_version >= '3.7' +pytest-mock==3.11.1 +pytest==7.3.2 +readme-renderer==37.3; python_version >= '3.7' +requests-toolbelt==1.0.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +requests==2.31.0 +rfc3986==2.0.0; python_version >= '3.7' +rich==13.4.2; python_version >= '3.7' +six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' +snowballstemmer==2.2.0 +sphinx-rtd-theme==1.2.2 +sphinx==6.2.1 +sphinxcontrib-applehelp==1.0.4; python_version >= '3.8' +sphinxcontrib-devhelp==1.0.2; python_version >= '3.5' +sphinxcontrib-htmlhelp==2.0.1; python_version >= '3.8' +sphinxcontrib-jquery==4.1; python_version >= '2.7' +sphinxcontrib-jsmath==1.0.1; python_version >= '3.5' +sphinxcontrib-qthelp==1.0.3; python_version >= '3.5' +sphinxcontrib-serializinghtml==1.1.5; python_version >= '3.5' +tomli==2.0.1; python_version < '3.11' and python_version < '3.11' +tomlkit==0.11.8; python_version >= '3.7' +tox-gh-actions==3.1.1 +tox==4.6.2 +twine==4.0.2 +typing-extensions==4.6.3; python_version < '3.11' and python_version < '3.10' +urllib3==2.0.3; python_full_version >= '3.7.0' +virtualenv==20.23.1; python_version >= '3.7' +webencodings==0.5.1 +wrapt==1.15.0; python_version < '3.11' +zipp==3.15.0; python_version >= '3.7' From 7e1fd78e083d50f83994e0bc7e30f8558c5bc614 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sun, 18 Jun 2023 00:22:56 +0200 Subject: [PATCH 07/37] Docs: fix path to sphinx configuration --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 45f0b96..68d4000 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -13,7 +13,7 @@ build: # Build documentation in the "docs/" directory with Sphinx sphinx: builder: html - configuration: docs/conf.py + configuration: docs/source/conf.py # Optional but recommended, declare the Python requirements required # to build your documentation From 7524e883a2a02817f6caf0415734ad8302667671 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sat, 15 Jul 2023 18:34:28 +0200 Subject: [PATCH 08/37] Docs: add myst-parser for .md files --- Pipfile | 1 + docs/requirements.txt | 37 ++++++++++++++++++++----------------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/Pipfile b/Pipfile index 956bb2c..5e947c1 100644 --- a/Pipfile +++ b/Pipfile @@ -16,3 +16,4 @@ twine = "*" tox-gh-actions = "*" sphinx = "*" sphinx-rtd-theme = "*" +myst-parser = "*" diff --git a/docs/requirements.txt b/docs/requirements.txt index 7eff0db..8b790d9 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -7,50 +7,53 @@ -i https://pypi.org/simple alabaster==0.7.13; python_version >= '3.6' -astroid==2.15.5; python_full_version >= '3.7.2' +astroid==2.15.6; python_full_version >= '3.7.2' babel==2.12.1; python_version >= '3.7' bleach==6.0.0; python_version >= '3.7' build==0.10.0 cachetools==5.3.1; python_version >= '3.7' certifi==2023.5.7; python_version >= '3.6' chardet==5.1.0; python_version >= '3.7' -charset-normalizer==3.1.0; python_full_version >= '3.7.0' +charset-normalizer==3.2.0; python_full_version >= '3.7.0' colorama==0.4.6; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6' dill==0.3.6; python_version < '3.11' distlib==0.3.6 docutils==0.18.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' -exceptiongroup==1.1.1; python_version < '3.11' +exceptiongroup==1.1.2; python_version < '3.11' filelock==3.12.2; python_version >= '3.7' idna==3.4; python_version >= '3.5' imagesize==1.4.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' -importlib-metadata==6.6.0; python_version < '3.10' and python_version < '3.12' +importlib-metadata==6.8.0; python_version < '3.12' iniconfig==2.0.0; python_version >= '3.7' isort==5.12.0; python_version >= '3.8' -jaraco.classes==3.2.3; python_version >= '3.7' +jaraco.classes==3.3.0; python_version >= '3.8' jinja2==3.1.2; python_version >= '3.7' -keyring==23.13.1; python_version >= '3.7' +keyring==24.2.0; python_version >= '3.8' lazy-object-proxy==1.9.0; python_version >= '3.7' markdown-it-py==3.0.0; python_version >= '3.8' markupsafe==2.1.3; python_version >= '3.7' mccabe==0.7.0; python_version >= '3.6' +mdit-py-plugins==0.4.0; python_version >= '3.8' mdurl==0.1.2; python_version >= '3.7' more-itertools==9.1.0; python_version >= '3.7' +myst-parser==2.0.0 packaging==23.1; python_version >= '3.7' pkginfo==1.9.6; python_version >= '3.6' -platformdirs==3.5.3; python_version >= '3.7' -pluggy==1.0.0; python_version >= '3.6' +platformdirs==3.8.1; python_version >= '3.7' +pluggy==1.2.0; python_version >= '3.7' pygments==2.15.1; python_version >= '3.7' pylint==2.17.4 -pyproject-api==1.5.2; python_version >= '3.7' +pyproject-api==1.5.3; python_version >= '3.7' pyproject-hooks==1.0.0; python_version >= '3.7' pytest-mock==3.11.1 -pytest==7.3.2 -readme-renderer==37.3; python_version >= '3.7' +pytest==7.4.0 +pyyaml==6.0; python_version >= '3.6' +readme-renderer==40.0; python_version >= '3.8' requests-toolbelt==1.0.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' requests==2.31.0 rfc3986==2.0.0; python_version >= '3.7' rich==13.4.2; python_version >= '3.7' -six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' +six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' snowballstemmer==2.2.0 sphinx-rtd-theme==1.2.2 sphinx==6.2.1 @@ -61,14 +64,14 @@ sphinxcontrib-jquery==4.1; python_version >= '2.7' sphinxcontrib-jsmath==1.0.1; python_version >= '3.5' sphinxcontrib-qthelp==1.0.3; python_version >= '3.5' sphinxcontrib-serializinghtml==1.1.5; python_version >= '3.5' -tomli==2.0.1; python_version < '3.11' and python_version < '3.11' +tomli==2.0.1; python_version < '3.11' tomlkit==0.11.8; python_version >= '3.7' -tox-gh-actions==3.1.1 -tox==4.6.2 +tox-gh-actions==3.1.3 +tox==4.6.4 twine==4.0.2 -typing-extensions==4.6.3; python_version < '3.11' and python_version < '3.10' +typing-extensions==4.7.1; python_version < '3.11' urllib3==2.0.3; python_full_version >= '3.7.0' virtualenv==20.23.1; python_version >= '3.7' webencodings==0.5.1 wrapt==1.15.0; python_version < '3.11' -zipp==3.15.0; python_version >= '3.7' +zipp==3.16.1; python_version >= '3.8' From 10e17f2278f1ab9400ae21d188dddb526f4d2579 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sat, 15 Jul 2023 18:40:05 +0200 Subject: [PATCH 09/37] Docs: Add myst-parser and fix docs in conf.py --- docs/source/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 5211cbc..eec89f2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -5,7 +5,7 @@ import os import sys -sys.path.insert(0, os.path.abspath('../../zephyr/')) +sys.path.insert(0, os.path.abspath('../../')) # -- Project information ----------------------------------------------------- @@ -19,7 +19,7 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ['sphinx.ext.autodoc'] +extensions = ['sphinx.ext.autodoc', 'myst_parser'] templates_path = ['_templates'] exclude_patterns = [] From 3c1d652c0c9adf00801ba0f7b95a7faf48fe36a0 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sat, 15 Jul 2023 18:36:14 +0200 Subject: [PATCH 10/37] Docs: place troubleshooting section and examples into .md files in docs folder --- README.md | 15 +++-- docs/source/examples.md | 56 +++++++++++++++++++ docs/source/examples.rst | 40 ------------- .../source/troubleshooting.md | 6 +- 4 files changed, 68 insertions(+), 49 deletions(-) create mode 100644 docs/source/examples.md delete mode 100644 docs/source/examples.rst rename TROUBLESHOOTING.md => docs/source/troubleshooting.md (56%) diff --git a/README.md b/README.md index d4d8dc7..4315171 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,11 @@ ![PyPI](https://img.shields.io/pypi/v/zephyr-python-api) ![PyPI - License](https://img.shields.io/pypi/l/zephyr-python-api) ### Project description -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. +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. + +For more detailed information please see [the project's documentation](https://zephyr-python-api.readthedocs.io/en/main/index.html). To be done: * More usage examples @@ -25,7 +29,7 @@ Zephyr Cloud auth: ```python from zephyr import ZephyrScale -zscale = ZephyrScale(token=) +zscale = ZephyrScale(token="") ``` Zephyr Server (TM4J) auth: @@ -41,7 +45,7 @@ auth = {"username": "", "password": ""} # or even session cookie dict auth = {"cookies": ""} -zscale = ZephyrScale.server_api(base_url=, **auth) +zscale = ZephyrScale.server_api(base_url="", **auth) ``` Then it is possible to interact with api wrappers: @@ -58,11 +62,6 @@ test_case = zapi.test_cases.get_test_case("") creation_result = zapi.test_cases.create_test_case("", "test_case_name") ``` -### Troubleshooting - -For troubleshooting see [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - - ### License This library is licensed under the Apache 2.0 License. diff --git a/docs/source/examples.md b/docs/source/examples.md new file mode 100644 index 0000000..cc443a6 --- /dev/null +++ b/docs/source/examples.md @@ -0,0 +1,56 @@ +# Examples + +## Authentication + +Zephyr Cloud auth requires generating an API access token ([see the how-to](https://support.smartbear.com/zephyr-scale-cloud/docs/rest-api/generating-api-access-tokens.html)). +```python +from zephyr import ZephyrScale + +zscale = ZephyrScale(token="") +``` + +Zephyr Server/Data Center (TM4J) auth: + +```python +from zephyr import ZephyrScale + +# Auth can be made with Jira token +auth = {"token": ""} + +# or with login and password (suggest using get_pass) +auth = {"username": "", "password": ""} + +# or even session cookie dict +auth = {"cookies": ""} + +zscale = ZephyrScale.server_api(base_url="", **auth) +``` + +## Usage + +Then it is possible to interact with the low-level api wrappers: +```python +zapi = zscale.api + +# Get all test cases +all_test_cases = zapi.test_cases.get_test_cases() + +# Get a single test case by its id +test_case = zapi.test_cases.get_test_case("") + +# Create a test case +creation_result = zapi.test_cases.create_test_case("", "test_case_name") +``` + +Each Zephyr Scale API wrapper group could be accessed as a property: +```python +zapi = zscale.api + +# Test Cases +test_case = zapi.test_cases + +# Test Cycles +test_cycles = zapi.test_cycles +``` + +Be aware that depending on your API type (Cloud or Server/Datacenter) the amount of wrapper groups and their naming is different. diff --git a/docs/source/examples.rst b/docs/source/examples.rst deleted file mode 100644 index 6d5fd06..0000000 --- a/docs/source/examples.rst +++ /dev/null @@ -1,40 +0,0 @@ -Examples -============= - -Authentication -------------- - -Zephyr Cloud auth: - -.. code-block:: python - - from zephyr import ZephyrScale - zscale = ZephyrScale(token=) - -Zephyr Server/Data Center (TM4J) auth: - -.. code-block:: python - - from zephyr import ZephyrScale - # Auth can be made with Jira token - auth = {"token": ""} - # or with login and password (suggest using get_pass) - auth = {"username": "", "password": ""} - # or even session cookie dict - auth = {"cookies": ""} - zscale = ZephyrScale.server_api(base_url=, **auth) - -Usage -------------- - -Then it is possible to interact with api wrappers: - -.. code-block:: python - - zapi = zscale.api - # Get all test cases - all_test_cases = zapi.test_cases.get_test_cases() - # Get a single test case by its id - test_case = zapi.test_cases.get_test_case("") - # Create a test case - creation_result = zapi.test_cases.create_test_case("", "test_case_name") diff --git a/TROUBLESHOOTING.md b/docs/source/troubleshooting.md similarity index 56% rename from TROUBLESHOOTING.md rename to docs/source/troubleshooting.md index 66dc5e4..6a798dd 100644 --- a/TROUBLESHOOTING.md +++ b/docs/source/troubleshooting.md @@ -1,5 +1,9 @@ +# Troubleshooting + +This section is for some troubleshooting advices and guides. + ## Reporting to Zephyr -- The Cucumber format is different from Behave reporter format. +The Cucumber tool format is different from python Behave reporter format. In case you want to report test executions from output Behave file, please use some custom formatter for Behave output, i.e. https://pypi.org/project/behave-cucumber-formatter/. From cb276f786ec702823d82d2eed18e6a160345da75 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sat, 15 Jul 2023 18:43:40 +0200 Subject: [PATCH 11/37] Docs: Add troubleshooting and zephyr sections to the docs, improve project description --- docs/source/index.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index e54f3e6..acb97b8 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,7 +6,13 @@ Welcome to Zephyr Python API's documentation! ============================================= -The package is a set of python wrappers for Zephyr Scale (TM4J) REST API (both Cloud and Server/DataCenter). Interact with Zephyr Scale without GUI, access it with python code and create automation scripts for your daily routines. +The package is a set of python wrappers for Zephyr Scale (TM4J) REST API (both Cloud and Server/DataCenter). +Interact with Zephyr Scale without GUI, access it with python code and create automation scripts for your daily routines. + +The idea of the package is to have two parts in it: a set of low-level wrappers and Zephyr objects (like a test case or a test cycle). +The low-level wrappers are simply performing requests to the API endpoints of Zephyr with no logic added. The Zephyr objects +is a set of classes where the Zephyr interaction logic is placed. The logic is implemented using the low-level API wrappers. +Currently the Zephyr objects are not implemented. .. toctree:: :maxdepth: 3 @@ -14,6 +20,8 @@ The package is a set of python wrappers for Zephyr Scale (TM4J) REST API (both C installation examples + troubleshooting + zephyr Limitations *********** @@ -21,7 +29,7 @@ Limitations The wrappers only implement public API methods from the official SmartBear Zephyr Scale Cloud and Server/DataCenter APIs. Useful links -*********** +************ `Zephyr Scale Cloud API docs `_ From 2aab942e78255be67764ff9b11c96928e0b6ba6a Mon Sep 17 00:00:00 2001 From: PetrS Date: Sat, 15 Jul 2023 19:04:14 +0200 Subject: [PATCH 12/37] Docs: Updated and populated zephyr_session module docstrings --- zephyr/scale/zephyr_session.py | 75 +++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/zephyr/scale/zephyr_session.py b/zephyr/scale/zephyr_session.py index ab233f1..4a7ce12 100644 --- a/zephyr/scale/zephyr_session.py +++ b/zephyr/scale/zephyr_session.py @@ -1,3 +1,6 @@ +""" +Module for Zephyr Scale session object. +""" import logging from urllib.parse import urlparse, parse_qs @@ -8,12 +11,12 @@ class InvalidAuthData(Exception): - """Invalid authentication data provided""" + """Raised when Invalid authentication data provided.""" class ZephyrSession: """ - Zephyr Scale basic session object. + Zephyr Scale basic session object. The authentication and response handling logic is placed here. :param base_url: url to make requests to :param token: auth token @@ -21,7 +24,7 @@ class ZephyrSession: :param password: password :param cookies: cookie dict - :keyword session_attrs: a dict with session attrs to be set as keys and their values + :param keyword session_attrs: a dict with session attrs to be set as keys and their values """ def __init__(self, base_url, token=None, username=None, password=None, cookies=None, **kwargs): self.base_url = base_url @@ -55,7 +58,17 @@ def _modify_session(self, **kwargs): setattr(self._session, session_attr, value) def _request(self, method: str, endpoint: str, return_raw: bool = False, **kwargs): - """General request wrapper with logging and handling response""" + """ + General request wrapper with logging and handling response + + :param method: request method + :param endpoint: endpoint to make request to + :param return_raw: whether to return raw response or not + + :raises: HTTPError if response status code is 400 or higher + + :return: response json, empty str or raw response + """ self.logger.debug(f"{method.capitalize()} data: endpoint={endpoint} and {kwargs}") url = self._create_url(endpoint) response = self._session.request(method=method, url=url, **kwargs) @@ -68,23 +81,58 @@ def _request(self, method: str, endpoint: str, return_raw: bool = False, **kwarg raise HTTPError(f"Error {response.status_code}. Response: {response.content}") def get(self, endpoint: str, params: dict = None, **kwargs): - """Get request wrapper""" + """ + Get request wrapper. + + :param endpoint: endpoint to make request to + :param params: dict with params to be passed to request + + :return: response json, empty str or raw response + """ return self._request("get", endpoint, params=params, **kwargs) def post(self, endpoint: str, json: dict = None, **kwargs): - """Post request wrapper""" + """ + Post request wrapper. + + :param endpoint: endpoint to make request to + :param json: json to be passed to request + + :return: response json, empty str or raw response + """ return self._request("post", endpoint, json=json, **kwargs) def put(self, endpoint: str, json: dict = None, **kwargs): - """Put request wrapper""" + """ + Put request wrapper + + :param endpoint: endpoint to make request to + :param json: json to be passed to request + + :return: response json, empty str or raw response + """ return self._request("put", endpoint, json=json, **kwargs) def delete(self, endpoint: str, **kwargs): - """Delete request wrapper""" + """ + Delete request wrapper. + + :param endpoint: endpoint to make request to + + :return: response json, empty str or raw response + """ return self._request("delete", endpoint, **kwargs) def get_paginated(self, endpoint, params=None): - """Get paginated data""" + """ + Get request wrapper for getting paginated data. Yields values from multiple get requests + responses. + + :param endpoint: endpoint to make request to + :param params: dict with params to be passed to request + + :return: generator with values from responses + """ self.logger.debug(f"Get paginated data from endpoint={endpoint} and params={params}") if params is None: params = {} @@ -103,8 +151,13 @@ def get_paginated(self, endpoint, params=None): def post_file(self, endpoint: str, file_path: str, to_files=None, **kwargs): """ - Post wrapper to send a file. Handles single file opening, - sending its content and closing + Post wrapper to send a file. Handles single file opening, sending its content and closing. + + :param endpoint: endpoint to make request to + :param file_path: path to file to be sent + :param to_files: dict with files to be sent along with the main file + + :return: response json, empty str or raw response """ with open(file_path, "rb") as file: files = {"file": file} From cbc4ace3d1adf21c4e5372dc7b65d5f0432fb27b Mon Sep 17 00:00:00 2001 From: PetrS Date: Sat, 15 Jul 2023 19:06:23 +0200 Subject: [PATCH 13/37] Docs: Updated and populated scale module docstrings --- zephyr/scale/scale.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/zephyr/scale/scale.py b/zephyr/scale/scale.py index 26a29ab..201fca1 100644 --- a/zephyr/scale/scale.py +++ b/zephyr/scale/scale.py @@ -1,3 +1,7 @@ +""" +A module with the Zephyr Scale base object. +""" + import logging from zephyr.scale.zephyr_session import ZephyrSession @@ -13,12 +17,16 @@ class ZephyrScale: """ - Zephyr Scale base object to interact with other objects or raw api by its methods. + Zephyr Scale base object to interact with raw Zephyr APIs or other Zephyr entities. You should + define the API version of Zephyr Scale instance you are going to work with. Server is v1 and + Cloud is v2. NOTE: Cloud API accepts only token auth, whereas Server API works with Jira auth methods. :param base_url: base API url to connect with :param api_version: 'v2' for Cloud and 'v1' for Server + + :raises ValueError: if api_version is not 'v1' or 'v2' """ def __init__(self, base_url=None, api_version=API_V2, **kwargs): base_url = DEFAULT_BASE_URL if not base_url else base_url @@ -34,5 +42,9 @@ def __init__(self, base_url=None, api_version=API_V2, **kwargs): @classmethod def server_api(cls, base_url, **kwargs): - """Alternative constructor for Zephyr Scale Server client""" + """ + Alternative constructor for Zephyr Scale Server client. + + :param base_url: base API url to connect with + """ return cls(base_url=base_url, api_version=API_V1, **kwargs) From afaee916e311a662da728528b801b38db8741378 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sat, 15 Jul 2023 19:23:46 +0200 Subject: [PATCH 14/37] Docs: Updated and populated server_api module docs --- zephyr/scale/server/server_api.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/zephyr/scale/server/server_api.py b/zephyr/scale/server/server_api.py index c52b5c6..414e948 100644 --- a/zephyr/scale/server/server_api.py +++ b/zephyr/scale/server/server_api.py @@ -1,60 +1,83 @@ +""" +A module with the Zephyr Scale Server Api wrapper class. +""" import logging from zephyr.scale.zephyr_session import ZephyrSession from zephyr.scale.server import endpoints -# pylint: disable=missing-function-docstring class ServerApiWrapper: - """Zephyr Scale Server Api wrapper""" + """ + Zephyr Scale Server Api wrapper class. It contains API endpoint wrappers for the Zephyr Scale + Server/Datacenter. The wrappers are grouped by the entity they are related to. + These wrapper groups are represented by the properties of the class. + + For more details on the API endpoints see docs: + https://support.smartbear.com/zephyr-scale-server/api-docs/v1/ + + :param session: ZephyrSession object with auth credentials + """ def __init__(self, session: ZephyrSession): self.session = session self.logger = logging.getLogger(__name__) @property def attachments(self): + """Attachment endpoints.""" return endpoints.AttachmentEndpoints(self.session) @property def automation(self): + """Automation endpoints.""" return endpoints.AutomationEndpoints(self.session) @property def custom_field(self): + """Custom field endpoints.""" return endpoints.CustomFieldEndpoints(self.session) @property def delete_execution(self): + """Delete execution endpoints.""" return endpoints.DeleteExecutionEndpoints(self.session) @property def environment(self): + """Environment endpoints.""" return endpoints.EnvironmentEndpoints(self.session) @property def folder(self): + """Folder endpoints.""" return endpoints.FolderEndpoints(self.session) @property def issue_link(self): + """Issue link endpoints.""" return endpoints.IssueLinkEndpoints(self.session) @property def project(self): + """Project endpoints.""" return endpoints.ProjectEndpoints(self.session) @property def test_cases(self): + """Test case endpoints.""" return endpoints.TestCaseEndpoints(self.session) @property def test_plans(self): + """Test plan endpoints.""" return endpoints.TestPlanEndpoints(self.session) @property def test_results(self): + """Test result endpoints.""" return endpoints.TestResultEndpoints(self.session) @property def test_runs(self): + """Test run endpoints.""" return endpoints.TestRunEndpoints(self.session) From 7488e64f3aa1b11ed596e6316dbba4feef7b7678 Mon Sep 17 00:00:00 2001 From: PetrS Date: Sat, 15 Jul 2023 19:24:50 +0200 Subject: [PATCH 15/37] Docs: fix a typo and doc string length in zephyr_session.py --- zephyr/scale/zephyr_session.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zephyr/scale/zephyr_session.py b/zephyr/scale/zephyr_session.py index 4a7ce12..9250973 100644 --- a/zephyr/scale/zephyr_session.py +++ b/zephyr/scale/zephyr_session.py @@ -1,5 +1,5 @@ """ -Module for Zephyr Scale session object. +A module for Zephyr Scale session object. """ import logging from urllib.parse import urlparse, parse_qs @@ -16,7 +16,8 @@ class InvalidAuthData(Exception): class ZephyrSession: """ - Zephyr Scale basic session object. The authentication and response handling logic is placed here. + Zephyr Scale basic session object. The authentication and response handling logic + is placed here. :param base_url: url to make requests to :param token: auth token From 89d1a25109cc24b5dc2d394bb93e40947ef01c5c Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 22 Oct 2023 21:46:06 +0200 Subject: [PATCH 16/37] Refactor Cloud API: Move EndpointTemplate to Zephyr session file --- zephyr/scale/server/endpoints/endpoints.py | 8 +------- zephyr/scale/zephyr_session.py | 6 ++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/zephyr/scale/server/endpoints/endpoints.py b/zephyr/scale/server/endpoints/endpoints.py index 8b3f9cc..cd9562b 100644 --- a/zephyr/scale/server/endpoints/endpoints.py +++ b/zephyr/scale/server/endpoints/endpoints.py @@ -1,13 +1,7 @@ -from ...zephyr_session import ZephyrSession +from ...zephyr_session import EndpointTemplate from .paths import ServerPaths as Paths -class EndpointTemplate: - """Class with basic constructor for endpoint classes""" - def __init__(self, session: ZephyrSession): - self.session = session - - class TestCaseEndpoints(EndpointTemplate): """Api wrapper for "Test Case" endpoints""" diff --git a/zephyr/scale/zephyr_session.py b/zephyr/scale/zephyr_session.py index 9250973..b21323e 100644 --- a/zephyr/scale/zephyr_session.py +++ b/zephyr/scale/zephyr_session.py @@ -167,3 +167,9 @@ def post_file(self, endpoint: str, file_path: str, to_files=None, **kwargs): files.update(to_files) return self._request("post", endpoint, files=files, **kwargs) + + +class EndpointTemplate: + """Class with basic constructor for endpoint classes""" + def __init__(self, session: ZephyrSession): + self.session = session From 35f1d9436124adc44acb2b2e5608c2efd0fd28f4 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 22 Oct 2023 22:31:57 +0200 Subject: [PATCH 17/37] Refactor Cloud API: Create cloud paths and endpoints files to be consistent with server part --- zephyr/scale/cloud/cloud_api.py | 40 +++--- zephyr/scale/cloud/endpoints/__init__.py | 2 +- zephyr/scale/cloud/endpoints/endpoints.py | 145 ++++++++++++++++++++++ zephyr/scale/cloud/endpoints/paths.py | 18 +++ 4 files changed, 180 insertions(+), 25 deletions(-) create mode 100644 zephyr/scale/cloud/endpoints/endpoints.py create mode 100644 zephyr/scale/cloud/endpoints/paths.py diff --git a/zephyr/scale/cloud/cloud_api.py b/zephyr/scale/cloud/cloud_api.py index e39977c..75d25d9 100644 --- a/zephyr/scale/cloud/cloud_api.py +++ b/zephyr/scale/cloud/cloud_api.py @@ -1,18 +1,10 @@ +""" +A module with the Zephyr Scale Cloud Api wrapper class. +""" import logging from zephyr.scale.zephyr_session import ZephyrSession -from zephyr.scale.cloud.endpoints import (AutomationEndpoints, - EnvironmentEndpoints, - FolderEndpoints, - HealthcheckEndpoints, - LinkEndpoints, - PriorityEndpoints, - ProjectEndpoints, - StatusEndpoints, - TestCaseEndpoints, - TestCycleEndpoints, - TestExecutionEndpoints, - TestPlanEndpoints) +from zephyr.scale.cloud import endpoints # pylint: disable=missing-function-docstring @@ -24,48 +16,48 @@ def __init__(self, session: ZephyrSession): @property def test_cases(self): - return TestCaseEndpoints(self.session) + return endpoints.TestCaseEndpoints(self.session) @property def test_cycles(self): - return TestCycleEndpoints(self.session) + return endpoints.TestCycleEndpoints(self.session) @property def test_plans(self): - return TestPlanEndpoints(self.session) + return endpoints.TestPlanEndpoints(self.session) @property def test_executions(self): - return TestExecutionEndpoints(self.session) + return endpoints.TestExecutionEndpoints(self.session) @property def folders(self): - return FolderEndpoints(self.session) + return endpoints.FolderEndpoints(self.session) @property def statuses(self): - return StatusEndpoints(self.session) + return endpoints.StatusEndpoints(self.session) @property def priorities(self): - return PriorityEndpoints(self.session) + return endpoints.PriorityEndpoints(self.session) @property def environments(self): - return EnvironmentEndpoints(self.session) + return endpoints.EnvironmentEndpoints(self.session) @property def projects(self): - return ProjectEndpoints(self.session) + return endpoints.ProjectEndpoints(self.session) @property def links(self): - return LinkEndpoints(self.session) + return endpoints.LinkEndpoints(self.session) @property def automations(self): - return AutomationEndpoints(self.session) + return endpoints.AutomationEndpoints(self.session) @property def healthcheck(self): - return HealthcheckEndpoints(self.session) + return endpoints.HealthcheckEndpoints(self.session) diff --git a/zephyr/scale/cloud/endpoints/__init__.py b/zephyr/scale/cloud/endpoints/__init__.py index 2b719b7..f4bec03 100644 --- a/zephyr/scale/cloud/endpoints/__init__.py +++ b/zephyr/scale/cloud/endpoints/__init__.py @@ -1,5 +1,5 @@ +from .endpoints import (TestCaseEndpoints) from .environments import EnvironmentEndpoints -from .test_cases import TestCaseEndpoints from .projects import ProjectEndpoints from .test_cycles import TestCycleEndpoints from .test_plans import TestPlanEndpoints diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py new file mode 100644 index 0000000..e0fdbd3 --- /dev/null +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -0,0 +1,145 @@ +from ...zephyr_session import EndpointTemplate +from .paths import CloudPaths as Paths + + +class TestCaseEndpoints(EndpointTemplate): + """Api wrapper for "Test Case" endpoints""" + + def get_test_cases(self, **kwargs): + """Retrieves all test cases. Query parameters can be used to filter the results. + + Keyword arguments: + :keyword projectKey: Jira project key filter + :keyword folderId: Folder ID filter + :keyword maxResults: A hint as to the maximum number of results to return in each call + :keyword startAt: Zero-indexed starting position. Should be a multiple of maxResults + :return: dict with response body + """ + return self.session.get_paginated(Paths.CASES, params=kwargs) + + def create_test_case(self, project_key: str, name: str, **kwargs): + """Creates a test case. Fields priorityName and statusName will be set to + default values if not informed. Default values are usually “Normal” + for priorityName and “Draft” for statusName. All required test case custom + fields should be present in the request. + + :param project_key: Jira project key + :param name: test case name + + Keyword arguments: + :keyword objective: A description of the objective + :keyword precondition: Any conditions that need to be met + :keyword estimatedTime: Estimated duration in milliseconds + :keyword componentId: ID of a component from Jira + :keyword priorityName: The priority name + :keyword statusName: The status name + :keyword folderId: ID of a folder to place the entity within + :keyword ownerId: Atlassian Account ID of the Jira user + :keyword labels: Array of labels associated to this entity + :return: dict with response body + """ + json = {"projectKey": project_key, + "name": name} + json.update(kwargs) + return self.session.post(Paths.CASES, json=json) + + def get_test_case(self, test_case_key: str): + """Returns a test case for the given key + + :param test_case_key: The key of the test case + :return: dict with response body + """ + return self.session.get(Paths.CASE_KEY.format(test_case_key)) + + def update_test_case(self, + test_case_key: str, + test_case_id: int, + name: str, + project_id: int, + priority_id: int, + status_id: int, + **kwargs): + """Updates an existing test case. + + :param test_case_key: The key of the test case + :param test_case_id: integer id of the test + :param name: test case nae + :param project_id: project id + :param priority_id: priority id + :param status_id: status id + + :return: dict with response body + """ + json = {"id": test_case_id, + "key": test_case_key, + "name": name, + "project": {"id": project_id}, + "priority": {"id": priority_id}, + "status": {"id": status_id}} + json.update(kwargs) + return self.session.put(Paths.CASE_KEY.format(test_case_key), + json=json) + + def get_links(self, test_case_key: str): + """Returns links for a test case with specified key""" + return self.session.get(Paths.CASE_LINKS.format(test_case_key)) + + def create_issue_links(self, test_case_key: str, issue_id: int): + """Creates a link between a test case and a Jira issue""" + json = {"issueId": issue_id} + return self.session.post(Paths.CASE_ISSUES.format(test_case_key), + json=json) + + def create_web_links(self, test_case_key: str, url: str, **kwargs): + """Creates a link between a test case and a generic URL""" + json = {"url": url} + json.update(kwargs) + return self.session.post(Paths.CASE_WEBLINKS.format(test_case_key), + json=json) + + def get_versions(self, test_case_key: str, **kwargs): + """ + Returns all test case versions for a test case with specified key. + Response is ordered by most recent first + """ + return self.session.get_paginated(Paths.CASE_VERS.format(test_case_key), + params=kwargs) + + def get_version(self, test_case_key: str, version: str): + """Retrieves a specific version of a test case""" + return self.session.get(Paths.CASE_VER.format(test_case_key, version)) + + def get_test_script(self, test_case_key: str): + """Returns the test script for the given test case""" + return self.session.get(Paths.CASE_SCRIPT.format(test_case_key)) + + def create_test_script(self, test_case_key: str, script_type: str, text: str): + """ + Creates or updates the test script for a test case. If the test case currently + has a sequence of test steps assigned to it, these will be implicitly removed. + """ + json = {"type": script_type, "text": text} + return self.session.post(Paths.CASE_SCRIPT.format(test_case_key), + json=json) + + def get_test_steps(self, test_case_key: str, **kwargs): + """ + Returns the test steps for the given test case. Provides a paged response, + with 100 items per page. + """ + return self.session.get_paginated(Paths.CASE_STEPS.format(test_case_key), + params=kwargs) + + def post_test_steps(self, test_case_key: str, mode: str, items: list): + """ + Assigns a series of test steps to a test case, appending them to any existing + sequence of test steps. A maximum of 100 steps can be posted per request. Consumers + should not attempt to parallelize this operation, as the order of the steps is defined + by the input order. If this endpoint is called on a test case that already has + a plain text or BDD test script, that test script will implicitly be removed. + All required step custom fields should be present in the request. + """ + json = {"mode": mode, + "items": items} + return self.session.post(Paths.CASE_STEPS.format(test_case_key), + json=json) diff --git a/zephyr/scale/cloud/endpoints/paths.py b/zephyr/scale/cloud/endpoints/paths.py new file mode 100644 index 0000000..cac1633 --- /dev/null +++ b/zephyr/scale/cloud/endpoints/paths.py @@ -0,0 +1,18 @@ +"""Paths to form Cloud API URLs""" + + +class CloudPaths: + """ + Zephyr Scale Cloud API paths based on: + https://support.smartbear.com/zephyr-scale-cloud/api-docs/ + """ + # Test Cases + CASES = "testcases" + CASE_KEY = "testcases/{}" + CASE_LINKS = "testcases/{}/links" + CASE_ISSUES = "testcases/{}/links/issues" + CASE_WEBLINKS = "testcases/{}/links/weblinks" + CASE_VERS = "testcases/{}/versions" + CASE_VER = "testcases/{}/versions/{}" + CASE_SCRIPT = "testcases/{}/testscript" + CASE_STEPS = "testcases/{}/teststeps" From 98558a00886d65b640d273d9472b939d62e90855 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 22 Oct 2023 22:33:53 +0200 Subject: [PATCH 18/37] Refactor Cloud API: Remove separate file with TestCaseEndpoints --- zephyr/scale/cloud/endpoints/test_cases.py | 139 --------------------- 1 file changed, 139 deletions(-) delete mode 100644 zephyr/scale/cloud/endpoints/test_cases.py diff --git a/zephyr/scale/cloud/endpoints/test_cases.py b/zephyr/scale/cloud/endpoints/test_cases.py deleted file mode 100644 index 390457d..0000000 --- a/zephyr/scale/cloud/endpoints/test_cases.py +++ /dev/null @@ -1,139 +0,0 @@ -from ...zephyr_session import ZephyrSession - - -class TestCaseEndpoints: - """Api wrapper for "Test Case" endpoints""" - def __init__(self, session: ZephyrSession): - self.session = session - - def get_test_cases(self, **kwargs): - """Retrieves all test cases. Query parameters can be used to filter the results. - - Keyword arguments: - :keyword projectKey: Jira project key filter - :keyword folderId: Folder ID filter - :keyword maxResults: A hint as to the maximum number of results to return in each call - :keyword startAt: Zero-indexed starting position. Should be a multiple of maxResults - :return: dict with response body - """ - return self.session.get_paginated("testcases", params=kwargs) - - def create_test_case(self, project_key: str, name: str, **kwargs): - """Creates a test case. Fields priorityName and statusName will be set to - default values if not informed. Default values are usually “Normal” - for priorityName and “Draft” for statusName. All required test case custom - fields should be present in the request. - - :param project_key: Jira project key - :param name: test case name - - Keyword arguments: - :keyword objective: A description of the objective - :keyword precondition: Any conditions that need to be met - :keyword estimatedTime: Estimated duration in milliseconds - :keyword componentId: ID of a component from Jira - :keyword priorityName: The priority name - :keyword statusName: The status name - :keyword folderId: ID of a folder to place the entity within - :keyword ownerId: Atlassian Account ID of the Jira user - :keyword labels: Array of labels associated to this entity - :return: dict with response body - """ - json = {"projectKey": project_key, - "name": name} - json.update(kwargs) - return self.session.post("testcases", json=json) - - def get_test_case(self, test_case_key: str): - """Returns a test case for the given key - - :param test_case_key: The key of the test case - :return: dict with response body - """ - return self.session.get(f"testcases/{test_case_key}") - - def update_test_case(self, - test_case_key: str, - test_case_id: int, - name: str, - project_id: int, - priority_id: int, - status_id: int, - **kwargs): - """Updates an existing test case. - - :param test_case_key: The key of the test case - :param test_case_id: integer id of the test - :param name: test case nae - :param project_id: project id - :param priority_id: priority id - :param status_id: status id - - :return: dict with response body - """ - json = {"id": test_case_id, - "key": test_case_key, - "name": name, - "project": {"id": project_id}, - "priority": {"id": priority_id}, - "status": {"id": status_id}} - json.update(kwargs) - return self.session.put(f"testcases/{test_case_key}", json=json) - - def get_links(self, test_case_key: str): - """Returns links for a test case with specified key""" - return self.session.get(f"testcases/{test_case_key}/links") - - def create_issue_links(self, test_case_key: str, issue_id: int): - """Creates a link between a test case and a Jira issue""" - json = {"issueId": issue_id} - return self.session.post(f"testcases/{test_case_key}/links/issues", json=json) - - def create_web_links(self, test_case_key: str, url: str, **kwargs): - """Creates a link between a test case and a generic URL""" - json = {"url": url} - json.update(kwargs) - return self.session.post(f"testcases/{test_case_key}/links/weblinks", json=json) - - def get_versions(self, test_case_key: str, **kwargs): - """ - Returns all test case versions for a test case with specified key. - Response is ordered by most recent first - """ - return self.session.get_paginated(f"testcases/{test_case_key}/versions", params=kwargs) - - def get_version(self, test_case_key: str, version: str): - """Retrieves a specific version of a test case""" - return self.session.get(f"testcases/{test_case_key}/versions/{version}") - - def get_test_script(self, test_case_key: str): - """Returns the test script for the given test case""" - return self.session.get(f"testcases/{test_case_key}/testscript") - - def create_test_script(self, test_case_key: str, script_type: str, text: str): - """ - Creates or updates the test script for a test case. If the test case currently - has a sequence of test steps assigned to it, these will be implicitly removed. - """ - json = {"type": script_type, "text": text} - return self.session.post(f"testcases/{test_case_key}/testscript", json=json) - - def get_test_steps(self, test_case_key: str, **kwargs): - """ - Returns the test steps for the given test case. Provides a paged response, - with 100 items per page. - """ - return self.session.get_paginated(f"testcases/{test_case_key}/teststeps", params=kwargs) - - def post_test_steps(self, test_case_key: str, mode: str, items: list): - """ - Assigns a series of test steps to a test case, appending them to any existing - sequence of test steps. A maximum of 100 steps can be posted per request. Consumers - should not attempt to parallelize this operation, as the order of the steps is defined - by the input order. If this endpoint is called on a test case that already has - a plain text or BDD test script, that test script will implicitly be removed. - All required step custom fields should be present in the request. - """ - json = {"mode": mode, - "items": items} - return self.session.post(f"testcases/{test_case_key}/teststeps", json=json) From f3e556dd0152c4fe911ddce5e0f1d0b80ffba0de Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 22 Oct 2023 22:39:03 +0200 Subject: [PATCH 19/37] Refactor Cloud API: Update CloudApiWrapper docstring --- zephyr/scale/cloud/cloud_api.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/zephyr/scale/cloud/cloud_api.py b/zephyr/scale/cloud/cloud_api.py index 75d25d9..3c9e103 100644 --- a/zephyr/scale/cloud/cloud_api.py +++ b/zephyr/scale/cloud/cloud_api.py @@ -9,7 +9,16 @@ # pylint: disable=missing-function-docstring class CloudApiWrapper: - """Zephyr Scale Cloud Api wrapper. Contains wrappers by sections.""" + """ + Zephyr Scale Cloud Api wrapper class. It contains API endpoint wrappers for the Zephyr Scale + Cloud. The wrappers are grouped by the entity they are related to. + These wrapper groups are represented by the properties of the class. + + For more details on the API endpoints see docs: + https://support.smartbear.com/zephyr-scale-cloud/api-docs/ + + :param session: ZephyrSession object with auth credentials + """ def __init__(self, session: ZephyrSession): self.session = session self.logger = logging.getLogger(__name__) From 468b2f68b216d5694f4c7e6fd6123517c5cf5289 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 22 Oct 2023 22:39:32 +0200 Subject: [PATCH 20/37] Refactor Cloud API: Update TestCaseEndpoints docstring in cloud paths --- zephyr/scale/cloud/endpoints/endpoints.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py index e0fdbd3..562c991 100644 --- a/zephyr/scale/cloud/endpoints/endpoints.py +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -3,7 +3,11 @@ class TestCaseEndpoints(EndpointTemplate): - """Api wrapper for "Test Case" endpoints""" + """ + Api wrapper for "Test Case" endpoints + + Details: https://support.smartbear.com/zephyr-scale-cloud/api-docs/#tag/Test-Cases + """ def get_test_cases(self, **kwargs): """Retrieves all test cases. Query parameters can be used to filter the results. From 3b5ed4ccc43b031b93c2f349df90f0ea583f9861 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 22 Oct 2023 23:13:51 +0200 Subject: [PATCH 21/37] Refactor Cloud API: Add Cloud API paths to paths file --- zephyr/scale/cloud/endpoints/paths.py | 58 +++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/zephyr/scale/cloud/endpoints/paths.py b/zephyr/scale/cloud/endpoints/paths.py index cac1633..f0377d0 100644 --- a/zephyr/scale/cloud/endpoints/paths.py +++ b/zephyr/scale/cloud/endpoints/paths.py @@ -16,3 +16,61 @@ class CloudPaths: CASE_VER = "testcases/{}/versions/{}" CASE_SCRIPT = "testcases/{}/testscript" CASE_STEPS = "testcases/{}/teststeps" + + # Test Cycles + CYCLES = "testcycles" + CYCLE_KEY = "testcycles/{}" + CYCLE_LINKS = "testcycles/{}/links" + CYCLE_ISSUES = "testcycles/{}/links/issues" + CYCLE_WEBLINKS = "testcycles/{}/links/weblinks" + + # Test Plans + PLANS = "testplans" + PLAN_KEY = "testplans/{}" + PLAN_WEBLINKS = "testplans/{}/links/weblinks" + PLAN_ISSUES = "testplans/{}/links/issues" + PLAN_CYCLES = "testplans/{}/links/testcycles" + + # Test Executions + EXECUTIONS = "testexecutions" + EXECUTIONS_KEY = "testexecutions/{}" + EXECUTIONS_LINKS = "testexecutions/{}/links" + EXECUTIONS_ISSUES = "testexecutions/{}/links/issues" + + # Folders + FOLDERS = "folders" + FOLDER_KEY = "folders/{}" + + # Statuses + STATUSES = "statuses" + STATUSES_ID = "statuses/{}" + + # Priorities + PRIORITIES = "priorities" + PRIORITIES_ID = "priorities/{}" + + # Environments + ENVIRONMENTS = "environments" + ENVIRONMENTS_ID = "environments/{}" + + # Projects + PROJECTS = "projects" + PROJECTS_KEY = "projects/{}" + + # Links + LINKS_ID = "links/{}" + + # Issue Links + ISLINKS_CASES = "issuelinks/{}/testcases" + ISLINKS_CYCLES = "issuelinks/{}/testcycles" + ISLINKS_PLANS = "issuelinks/{}/testplans" + ISLINKS_EXECS = "issuelinks/{}/executions" + + # Automations + AUT_CUSTOM = "automations/executions/custom" + AUT_CUCUMBER = "automations/executions/cucumber" + AUT_JUNIT = "automations/executions/junit" + AUT_CASES = "automations/testcases" + + # Healthcheck + HEALTHCHECK = "healthcheck" From a665f3e9bf5009b8b226798dcdb8b1e5548d70f4 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Fri, 5 Jan 2024 21:00:16 +0100 Subject: [PATCH 22/37] Refactor Cloud API: Move TestCycleEndpoints to the endpoints file --- zephyr/scale/cloud/endpoints/__init__.py | 4 +- zephyr/scale/cloud/endpoints/endpoints.py | 103 ++++++++++++++++++++ zephyr/scale/cloud/endpoints/test_cycles.py | 66 ------------- 3 files changed, 105 insertions(+), 68 deletions(-) delete mode 100644 zephyr/scale/cloud/endpoints/test_cycles.py diff --git a/zephyr/scale/cloud/endpoints/__init__.py b/zephyr/scale/cloud/endpoints/__init__.py index f4bec03..bf8d05f 100644 --- a/zephyr/scale/cloud/endpoints/__init__.py +++ b/zephyr/scale/cloud/endpoints/__init__.py @@ -1,7 +1,7 @@ -from .endpoints import (TestCaseEndpoints) +from .endpoints import (TestCaseEndpoints, + TestCycleEndpoints) from .environments import EnvironmentEndpoints from .projects import ProjectEndpoints -from .test_cycles import TestCycleEndpoints from .test_plans import TestPlanEndpoints from .test_executions import TestExecutionEndpoints from .folders import FolderEndpoints diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py index 562c991..073b1d1 100644 --- a/zephyr/scale/cloud/endpoints/endpoints.py +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -1,3 +1,5 @@ +from typing import Union + from ...zephyr_session import EndpointTemplate from .paths import CloudPaths as Paths @@ -147,3 +149,104 @@ def post_test_steps(self, test_case_key: str, mode: str, items: list): "items": items} return self.session.post(Paths.CASE_STEPS.format(test_case_key), json=json) + + +class TestCycleEndpoints(EndpointTemplate): + """ + Api wrapper for "Test Cycle" endpoints + + Details: https://support.smartbear.com/zephyr-scale-cloud/api-docs/#tag/Test-Cycles + """ + + def get_test_cycles(self, **kwargs): + """Retrieves all test cycles. Query parameters can be used to filter the results. + + Keyword arguments: + :keyword projectKey: Jira project key filter + :keyword folderId: Folder ID filter + :keyword maxResults: A hint as to the maximum number of results to return in each call + :keyword startAt: Zero-indexed starting position. Should be a multiple of maxResults + :return: dict with response body + """ + return self.session.get_paginated(Paths.CYCLES, params=kwargs) + + def create_test_cycle(self, project_key: str, name: str, **kwargs): + """Creates a test cycle. All required test cycle custom fields should be present in the request. + + :param project_key: Jira project key + :param name: test cycle name + :return: dict with response body + """ + json = {"projectKey": project_key, + "name": name} + json.update(kwargs) + return self.session.post(Paths.CYCLES, json=json) + + def get_test_cycle(self, test_cycle_id_or_key: Union[str, int]): + """ + Returns a test cycle for the given key + + :pqram test_cycle_id_or_key: The ID or key of the test cycle + :return: dict with response body + """ + return self.session.get(Paths.CYCLE_KEY.format(test_cycle_id_or_key)) + + def update_test_cycle(self, + test_cycle_key: str, + test_cycle_id: int, + name: str, + project_id: str, + status_id: str, + **kwargs): + """ + Updates an existing test cycle. If the project has test cycle + custom fields, all custom fields should be present in the request. + To leave any of them blank, please set them null + if they are not required custom fields. + + :param test_cycle_key: The ID or key of the test cycle + :param test_cycle_id: integer id of the test cycle + :param name: Name of the Test Cycle + :param project_id: project id + :param status_id: status id + :return: dict with response body + """ + json = {"id": test_cycle_id, + "key": test_cycle_key, + "name": name, + "project": {"id": project_id}, + "status": {"id": status_id}} + json.update(kwargs) + return self.session.put(Paths.CYCLE_KEY, json=json) + + def get_links(self, test_cycle_id_or_key: Union[str, int]): + """ + Returns links for a test cycle with specified key + + :param test_cycle_id_or_key: The ID or key of the test cycle + """ + return self.session.get(Paths.CYCLE_LINKS.format(test_cycle_id_or_key)) + + def create_issue_links(self, test_cycle_id_or_key: Union[str, int], issue_id: int): + """ + Creates a link between a test cycle and a Jira issue + + :param test_cycle_id_or_key: The ID or key of the test cycle + :param issue_id: The id of the issue + :return: dict with response body + """ + json = {"issueId": issue_id} + return self.session.post(Paths.CYCLE_ISSUES.format(test_cycle_id_or_key), + json=json) + + def create_web_links(self, test_cycle_id_or_key: Union[str, int], url: str, **kwargs): + """ + Creates a link between a test cycle and a generic URL + + :param test_cycle_id_or_key: The ID or key of the test cycle + :param url: The web link URL + """ + json = {"url": url} + json.update(kwargs) + return self.session.post(Paths.CYCLE_WEBLINKS.format(test_cycle_id_or_key), + json=json) diff --git a/zephyr/scale/cloud/endpoints/test_cycles.py b/zephyr/scale/cloud/endpoints/test_cycles.py deleted file mode 100644 index 5776fee..0000000 --- a/zephyr/scale/cloud/endpoints/test_cycles.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import Union - -from ...zephyr_session import ZephyrSession - - -class TestCycleEndpoints: - """Api wrapper for "Test Cycle" endpoints""" - def __init__(self, session: ZephyrSession): - self.session = session - - def get_all_test_cycles(self, **kwargs): - """ - Returns all test cycles. Query parameters can be used to filter - by project and folder - """ - return self.session.get_paginated("testcycles", params=kwargs) - - def create_test_cycle(self, project_key: str, name: str, **kwargs): - """ - Creates a Test Cycle. All required test cycle custom fields - should be present in the request - """ - json = {"projectKey": project_key, - "name": name} - json.update(kwargs) - return self.session.post("testcycles", json=json) - - def get_test_cycle(self, test_cycle_id_or_key: Union[str, int]): - """Returns a test cycle for the given key""" - return self.session.get(f"testcycles/{test_cycle_id_or_key}") - - def update_test_cycle(self, - test_cycle_key: str, - test_cycle_id: int, - name: str, - project_id: str, - status_id: str, - **kwargs): - """ - Updates an existing test cycle. If the project has test cycle - custom fields, all custom fields should be present in the request. - To leave any of them blank, please set them null - if they are not required custom fields. - """ - json = {"id": test_cycle_id, - "key": test_cycle_key, - "name": name, - "project": {"id": project_id}, - "status": {"id": status_id}} - json.update(kwargs) - return self.session.put(f"testcycles/{test_cycle_key}", json=json) - - def get_links(self, test_cycle_id_or_key: Union[str, int]): - """Returns links for a test cycle with specified key""" - return self.session.get(f"testcycles/{test_cycle_id_or_key}/links") - - def create_issue_links(self, test_cycle_id_or_key: Union[str, int], issue_id: int): - """Creates a link between a test cycle and a Jira issue""" - json = {"issueId": issue_id} - return self.session.post(f"testcycles/{test_cycle_id_or_key}/links/issues", json=json) - - def create_web_links(self, test_cycle_id_or_key: Union[str, int], url: str, **kwargs): - """Creates a link between a test cycle and a generic URL""" - json = {"url": url} - json.update(kwargs) - return self.session.post(f"testcycles/{test_cycle_id_or_key}/links/weblinks", json=json) From 6198ff42475e7be485c0edafe6f318e260523a43 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 21 Apr 2024 16:19:13 +0200 Subject: [PATCH 23/37] #23: add missing endpoint for test executions with test steps --- zephyr/scale/cloud/endpoints/paths.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zephyr/scale/cloud/endpoints/paths.py b/zephyr/scale/cloud/endpoints/paths.py index f0377d0..83b69ef 100644 --- a/zephyr/scale/cloud/endpoints/paths.py +++ b/zephyr/scale/cloud/endpoints/paths.py @@ -36,6 +36,7 @@ class CloudPaths: EXECUTIONS_KEY = "testexecutions/{}" EXECUTIONS_LINKS = "testexecutions/{}/links" EXECUTIONS_ISSUES = "testexecutions/{}/links/issues" + EXECUTIONS_STEPS = "testexecutions/{}/teststeps" # Folders FOLDERS = "folders" From 52de5bbb6e64abc3b5378ae3f56840affd1fb87b Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 21 Apr 2024 19:05:15 +0200 Subject: [PATCH 24/37] #23: update TestCycleEndpoints.create_web_links docstring with :return: --- zephyr/scale/cloud/endpoints/endpoints.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py index 073b1d1..f33a3d0 100644 --- a/zephyr/scale/cloud/endpoints/endpoints.py +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -245,6 +245,7 @@ def create_web_links(self, test_cycle_id_or_key: Union[str, int], url: str, **kw :param test_cycle_id_or_key: The ID or key of the test cycle :param url: The web link URL + :return: dict with response body """ json = {"url": url} json.update(kwargs) From aec733f8560521d74113dea7d3719a8a98ebcd1c Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 21 Apr 2024 19:05:57 +0200 Subject: [PATCH 25/37] #23: Move TestPlanEndpoints to endpoints.py --- zephyr/scale/cloud/endpoints/__init__.py | 4 +- zephyr/scale/cloud/endpoints/endpoints.py | 71 ++++++++++++++++++++++ zephyr/scale/cloud/endpoints/test_plans.py | 18 ------ 3 files changed, 73 insertions(+), 20 deletions(-) delete mode 100644 zephyr/scale/cloud/endpoints/test_plans.py diff --git a/zephyr/scale/cloud/endpoints/__init__.py b/zephyr/scale/cloud/endpoints/__init__.py index bf8d05f..e9c200e 100644 --- a/zephyr/scale/cloud/endpoints/__init__.py +++ b/zephyr/scale/cloud/endpoints/__init__.py @@ -1,8 +1,8 @@ from .endpoints import (TestCaseEndpoints, - TestCycleEndpoints) + TestCycleEndpoints, + TestPlanEndpoints) from .environments import EnvironmentEndpoints from .projects import ProjectEndpoints -from .test_plans import TestPlanEndpoints from .test_executions import TestExecutionEndpoints from .folders import FolderEndpoints from .statuses import StatusEndpoints diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py index f33a3d0..737b60c 100644 --- a/zephyr/scale/cloud/endpoints/endpoints.py +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -251,3 +251,74 @@ def create_web_links(self, test_cycle_id_or_key: Union[str, int], url: str, **kw json.update(kwargs) return self.session.post(Paths.CYCLE_WEBLINKS.format(test_cycle_id_or_key), json=json) + + +class TestPlanEndpoints(EndpointTemplate): + """Api wrapper for "Test Plan" endpoints""" + + def get_test_plans(self, **kwargs): + """Retrieves all test plans. Query parameters can be used to filter the results. + + Keyword arguments: + :keyword projectKey: Jira project key filter + :keyword maxResults: A hint as to the maximum number of results to return in each call + :keyword startAt: Zero-indexed starting position. Should be a multiple of maxResults + :return: dict with response body + """ + return self.session.get_paginated(Paths.PLANS, params=kwargs) + + def create_test_plan(self, project_key: str, name: str, **kwargs): + """Creates a test plan. All required test plan custom fields should be present in the request. + + :param project_key: Jira project key + :param name: test plan name + :return: dict with response body + """ + json = {"projectKey": project_key, + "name": name} + json.update(kwargs) + return self.session.post(Paths.PLANS, json=json) + + def get_test_plan(self, test_plan_key: Union[str, int]): + """ + Returns a test plan for the given id or key. + + :param test_plan_key: The ID or key of the test plan. Test plan keys are of the format [A-Z]+-P[0-9]+ + :return: dict with response body + """ + return self.session.get(Paths.PLAN_KEY.format(test_plan_key)) + + def create_web_links(self, test_plan_key: Union[str, int], url: str, description: str): + """ + Creates a link between a test plan and a generic URL. + + :param test_plan_key: The ID or key of the test plan. Test plan keys are of the format [A-Z]+-P[0-9]+ + :param url: The web link URL + :param description: The link description + :return: dict with response body + """ + json = {"url": url, "description": description} + return self.session.post(Paths.PLAN_WEBLINKS.format(test_plan_key), + json=json) + + def create_issue_link(self, test_plan_key: Union[str, int], issue_id: int): + """ + Creates a link between a test plan and a Jira issue. + + :param test_plan_key: The ID or key of the test plan. Test plan keys are of the format [A-Z]+-P[0-9]+ + :param issue_id: The issue ID + :return: dict with response body + """ + return self.session.post(Paths.PLAN_ISSUES.format(test_plan_key), + json={"issueId": issue_id}) + + def create_test_cycle_link(self, test_plan_key: Union[str, int], test_cycle_id: int): + """ + Creates a link between a test plan and a test cycle. + + :param test_plan_key: The ID or key of the test plan. Test plan keys are of the format [A-Z]+-P[0-9]+ + :param test_cycle_id: The ID or key of the test cycle. Test cycle keys are of the format [A-Z]+-R[0-9]+ + :return: dict with response body + """ + return self.session.post(Paths.PLAN_CYCLES.format(test_plan_key), + json={"testCycleIdOrKey": test_cycle_id}) diff --git a/zephyr/scale/cloud/endpoints/test_plans.py b/zephyr/scale/cloud/endpoints/test_plans.py deleted file mode 100644 index 4c964e4..0000000 --- a/zephyr/scale/cloud/endpoints/test_plans.py +++ /dev/null @@ -1,18 +0,0 @@ -from ...zephyr_session import ZephyrSession - - -class TestPlanEndpoints: - """Api wrapper for "Test Plan" endpoints""" - def __init__(self, session: ZephyrSession): - self.session = session - - def get_test_plans(self, **kwargs): - """Retrieves all test plans. Query parameters can be used to filter the results. - - Keyword arguments: - :keyword projectKey: Jira project key filter - :keyword maxResults: A hint as to the maximum number of results to return in each call - :keyword startAt: Zero-indexed starting position. Should be a multiple of maxResults - :return: dict with response body - """ - return self.session.get_paginated("testplans", params=kwargs) From 846cc962a715f46dc05845d7d55f8a4dab4ccf20 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 21 Apr 2024 19:24:58 +0200 Subject: [PATCH 26/37] #23: Move TestExecutionEndpoints to endpoints.py --- zephyr/scale/cloud/endpoints/__init__.py | 4 +- zephyr/scale/cloud/endpoints/endpoints.py | 77 +++++++++++++++++++ .../scale/cloud/endpoints/test_executions.py | 46 ----------- 3 files changed, 79 insertions(+), 48 deletions(-) delete mode 100644 zephyr/scale/cloud/endpoints/test_executions.py diff --git a/zephyr/scale/cloud/endpoints/__init__.py b/zephyr/scale/cloud/endpoints/__init__.py index e9c200e..881dce3 100644 --- a/zephyr/scale/cloud/endpoints/__init__.py +++ b/zephyr/scale/cloud/endpoints/__init__.py @@ -1,9 +1,9 @@ from .endpoints import (TestCaseEndpoints, TestCycleEndpoints, - TestPlanEndpoints) + TestPlanEndpoints, + TestExecutionEndpoints) from .environments import EnvironmentEndpoints from .projects import ProjectEndpoints -from .test_executions import TestExecutionEndpoints from .folders import FolderEndpoints from .statuses import StatusEndpoints from .priorities import PriorityEndpoints diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py index 737b60c..0568a2d 100644 --- a/zephyr/scale/cloud/endpoints/endpoints.py +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -322,3 +322,80 @@ def create_test_cycle_link(self, test_plan_key: Union[str, int], test_cycle_id: """ return self.session.post(Paths.PLAN_CYCLES.format(test_plan_key), json={"testCycleIdOrKey": test_cycle_id}) + + +class TestExecutionEndpoints(EndpointTemplate): + """Api wrapper for "Test Execution" endpoints""" + + def get_test_executions(self, **kwargs): + """ + Returns all test executions. Query parameters can be used to filter + by project and folder + """ + return self.session.get_paginated(Paths.EXECUTIONS, params=kwargs) + + def create_test_execution(self, + project_key: str, + test_case_key: str, + test_cycle_key: str, + status_name: str, + **kwargs): + """ + Creates a test execution. All required test execution custom fields + should be present in the request + """ + json = {"projectKey": project_key, + "testCaseKey": test_case_key, + "testCycleKey": test_cycle_key, + "statusName": status_name} + json.update(kwargs) + return self.session.post(Paths.EXECUTIONS, json=json) + + def get_test_execution(self, test_execution_id_or_key: Union[str, int], **kwargs): + """ + Returns a test execution for the given ID + + :param test_execution_id_or_key: The ID or key of the test execution. + Test execution keys are of the format [A-Z]+-E[0-9]+ + :return: dict with response body + """ + return self.session.get(Paths.EXECUTIONS_KEY.format(test_execution_id_or_key), params=kwargs) + + def update_test_execution(self, test_execution_id_or_key: Union[str, int], **kwargs): + """ + Update the test execution. + + :param test_execution_id_or_key: The ID or key of the test execution. + Test execution keys are of the format [A-Z]+-E[0-9]+ + :return: dict with response body + """ + return self.session.put(Paths.EXECUTIONS_KEY.format(test_execution_id_or_key), json=kwargs) + + def get_test_steps(self, test_execution_id_or_key: Union[str, int], **kwargs): + """ + Returns the test steps for the given test execution. Provides a paged response. + + :param test_execution_id_or_key: The ID or key of the test execution. + Test execution keys are of the format [A-Z]+-E[0-9]+ + """ + return self.session.get_paginated(Paths.EXECUTIONS_STEPS.format(test_execution_id_or_key), + params=kwargs) + + def get_links(self, test_execution_id_or_key: Union[str, int]): + """ + Returns links for a test execution with specified ID + + :param test_execution_id_or_key: The ID or key of the test execution. + """ + return self.session.get(Paths.EXECUTIONS_LINKS.format(test_execution_id_or_key)) + + def create_issue_links(self, test_execution_id_or_key: Union[str, int], issue_id: int): + """ + Creates a link between a test execution and a Jira issue + + :param test_execution_id_or_key: The ID or key of the test execution. + :param issue_id: The id of the issue + :return: dict with response body + """ + return self.session.post(Paths.EXECUTIONS_ISSUES.format(test_execution_id_or_key), + json={"issueId": issue_id}) diff --git a/zephyr/scale/cloud/endpoints/test_executions.py b/zephyr/scale/cloud/endpoints/test_executions.py deleted file mode 100644 index 5ec63d0..0000000 --- a/zephyr/scale/cloud/endpoints/test_executions.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import Union - -from ...zephyr_session import ZephyrSession - - -class TestExecutionEndpoints: - """Api wrapper for "Test Execution" endpoints""" - def __init__(self, session: ZephyrSession): - self.session = session - - def get_test_executions(self, **kwargs): - """ - Returns all test executions. Query parameters can be used to filter - by project and folder - """ - return self.session.get_paginated("testexecutions", params=kwargs) - - def create_test_execution(self, - project_key: str, - test_case_key: str, - test_cycle_key: str, - status_name: str, - **kwargs): - """ - Creates a test execution. All required test execution custom fields - should be present in the request - """ - json = {"projectKey": project_key, - "testCaseKey": test_case_key, - "testCycleKey": test_cycle_key, - "statusName": status_name} - json.update(kwargs) - return self.session.post("testexecutions", json=json) - - def get_test_execution(self, test_execution_id_or_key: Union[str, int], **kwargs): - """Returns a test execution for the given ID""" - return self.session.get(f"testexecutions/{test_execution_id_or_key}", params=kwargs) - - def get_links(self, test_execution_id_or_key: Union[str, int]): - """Returns links for a test execution with specified ID""" - return self.session.get(f"testexecutions/{test_execution_id_or_key}/links") - - def create_issue_links(self, test_execution_id_or_key: Union[str, int], issue_id: int): - """Creates a link between a test execution and a Jira issue""" - return self.session.post(f"testexecutions/{test_execution_id_or_key}/links", - json={"issueId": issue_id}) From 980453a430a37675ce5f0537db31ca535c57342c Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 21 Apr 2024 19:44:32 +0200 Subject: [PATCH 27/37] #23: Move FolderEndpoints,StatusEndpoints to endpoints.py --- zephyr/scale/cloud/endpoints/__init__.py | 6 +- zephyr/scale/cloud/endpoints/endpoints.py | 89 +++++++++++++++++++++++ zephyr/scale/cloud/endpoints/folders.py | 24 ------ zephyr/scale/cloud/endpoints/statuses.py | 16 ---- 4 files changed, 92 insertions(+), 43 deletions(-) delete mode 100644 zephyr/scale/cloud/endpoints/folders.py delete mode 100644 zephyr/scale/cloud/endpoints/statuses.py diff --git a/zephyr/scale/cloud/endpoints/__init__.py b/zephyr/scale/cloud/endpoints/__init__.py index 881dce3..6970fae 100644 --- a/zephyr/scale/cloud/endpoints/__init__.py +++ b/zephyr/scale/cloud/endpoints/__init__.py @@ -1,11 +1,11 @@ from .endpoints import (TestCaseEndpoints, TestCycleEndpoints, TestPlanEndpoints, - TestExecutionEndpoints) + TestExecutionEndpoints, + FolderEndpoints, + StatusEndpoints) from .environments import EnvironmentEndpoints from .projects import ProjectEndpoints -from .folders import FolderEndpoints -from .statuses import StatusEndpoints from .priorities import PriorityEndpoints from .links import LinkEndpoints from .healthcheck import HealthcheckEndpoints diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py index 0568a2d..c0f00ea 100644 --- a/zephyr/scale/cloud/endpoints/endpoints.py +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -399,3 +399,92 @@ def create_issue_links(self, test_execution_id_or_key: Union[str, int], issue_id """ return self.session.post(Paths.EXECUTIONS_ISSUES.format(test_execution_id_or_key), json={"issueId": issue_id}) + + +class FolderEndpoints(EndpointTemplate): + """Api wrapper for "Folder" endpoints""" + + def get_folders(self, **kwargs): + """ + Returns all folder. + """ + return self.session.get_paginated(Paths.FOLDERS, params=kwargs) + + def create_folder(self, name: str, project_key: str, folder_type: str, **kwargs): + """ + Creates a folder. + + :param name: folder name + :param project_key: Jira project key + :param folder_type: Folder type. Valid values: "TEST_CASE", "TEST_PLAN", "TEST_CYCLE" + :return: dict with response body + """ + json = {"name": name, + "projectKey": project_key, + "folderType": folder_type} + json.update(kwargs) + return self.session.post(Paths.FOLDERS, json=json) + + def get_folder(self, folder_id: int): + """ + Returns a folder for the given ID. + + :param folder_id: Folder ID + :return: dict with response body + """ + return self.session.get(Paths.FOLDER_KEY.format(folder_id)) + + +class StatusEndpoints(EndpointTemplate): + """Api wrapper for "Status" endpoints""" + + def get_statuses(self, **kwargs): + """Returns all statuses""" + return self.session.get_paginated(Paths.STATUSES, params=kwargs) + + def create_status(self, project_key: str, status_name: str, status_type: str, **kwargs): + """ + Creates a status. + + :param project_key: Jira project key + :param status_name: The status name + :param status_type: The status type. Valid values: "TEST_CASE", "TEST_PLAN", "TEST_CYCLE", "TEST_EXECUTION" + :return: dict with response body + """ + json = {"projectKey": project_key, + "name": status_name, + "type": status_type} + json.update(kwargs) + return self.session.post(Paths.STATUSES, json=json) + + def get_status(self, status_id: int): + """ + Returns a status for the given ID + + :param status_id: Status ID + :return: dict with response body + """ + return self.session.get(Paths.STATUSES_ID.format(status_id)) + + def update_status(self, status_id: int, project_id: int, status_name: str, + index: int, archived: bool, default: bool, **kwargs): + """ + Update an existing status. Please take into account that for each non-specified field + the value will be cleared. + + :param status_id: Status ID + :param project_id: Project ID + :param status_name: The status name + :param index: The status index + :param archived: The status archived flag + :param default: The status default flag + :return: dict with response body + """ + json = {"id": status_id, + "project": {"id": project_id}, + "name": status_name, + "index": index, + "archived": archived, + "default": default} + json.update(kwargs) + return self.session.put(Paths.STATUSES_ID.format(status_id), json=json) diff --git a/zephyr/scale/cloud/endpoints/folders.py b/zephyr/scale/cloud/endpoints/folders.py deleted file mode 100644 index 8b7c332..0000000 --- a/zephyr/scale/cloud/endpoints/folders.py +++ /dev/null @@ -1,24 +0,0 @@ -from ...zephyr_session import ZephyrSession - - -class FolderEndpoints: - """Api wrapper for "Folder" endpoints""" - - def __init__(self, session: ZephyrSession): - self.session = session - - def get_folders(self, **kwargs): - """Returns all folders""" - return self.session.get_paginated("folders", params=kwargs) - - def create_folder(self, name, project_key, folder_type, **kwargs): - """Creates a folder""" - json = {"name": name, - "projectKey": project_key, - "folderType": folder_type} - json.update(kwargs) - return self.session.post("folders", json=json) - - def get_folder(self, folder_id): - """Returns a folder for the given ID""" - return self.session.get(f"folders/{folder_id}") diff --git a/zephyr/scale/cloud/endpoints/statuses.py b/zephyr/scale/cloud/endpoints/statuses.py deleted file mode 100644 index 1f977d1..0000000 --- a/zephyr/scale/cloud/endpoints/statuses.py +++ /dev/null @@ -1,16 +0,0 @@ -from ...zephyr_session import ZephyrSession - - -class StatusEndpoints: - """Api wrapper for "Status" endpoints""" - - def __init__(self, session: ZephyrSession): - self.session = session - - def get_statuses(self, **kwargs): - """Returns all statuses""" - return self.session.get_paginated("statuses", params=kwargs) - - def get_status(self, status_id): - """Returns a status for the given ID""" - return self.session.get(f"statuses/{status_id}") From b73ca139288c6a00a3043e36cb7fa19a57fcc084 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 21 Apr 2024 19:54:42 +0200 Subject: [PATCH 28/37] #23: Move PriorityEndpoints to endpoints.py --- zephyr/scale/cloud/endpoints/__init__.py | 4 +- zephyr/scale/cloud/endpoints/endpoints.py | 51 ++++++++++++++++++++++ zephyr/scale/cloud/endpoints/priorities.py | 16 ------- 3 files changed, 53 insertions(+), 18 deletions(-) delete mode 100644 zephyr/scale/cloud/endpoints/priorities.py diff --git a/zephyr/scale/cloud/endpoints/__init__.py b/zephyr/scale/cloud/endpoints/__init__.py index 6970fae..6fa2590 100644 --- a/zephyr/scale/cloud/endpoints/__init__.py +++ b/zephyr/scale/cloud/endpoints/__init__.py @@ -3,10 +3,10 @@ TestPlanEndpoints, TestExecutionEndpoints, FolderEndpoints, - StatusEndpoints) + StatusEndpoints, + PriorityEndpoints) from .environments import EnvironmentEndpoints from .projects import ProjectEndpoints -from .priorities import PriorityEndpoints from .links import LinkEndpoints from .healthcheck import HealthcheckEndpoints from .automations import AutomationEndpoints diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py index c0f00ea..e80772b 100644 --- a/zephyr/scale/cloud/endpoints/endpoints.py +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -488,3 +488,54 @@ def update_status(self, status_id: int, project_id: int, status_name: str, "default": default} json.update(kwargs) return self.session.put(Paths.STATUSES_ID.format(status_id), json=json) + + +class PriorityEndpoints(EndpointTemplate): + """Api wrapper for "Priority" endpoints""" + + def get_priorities(self, **kwargs): + """Returns all priorities""" + return self.session.get_paginated(Paths.PRIORITIES, params=kwargs) + + def create_priority(self, project_key: str, priority_name: str, **kwargs): + """ + Creates a priority. + + :param project_key: Jira project key + :param priority_name: The priority name + :return: dict with response body + """ + json = {"projectKey": project_key, + "name": priority_name} + json.update(kwargs) + return self.session.post(Paths.PRIORITIES, json=json) + + def get_priority(self, priority_id: int): + """ + Returns a priority for the given ID + + :param priority_id: Priority ID + :return: dict with response body + """ + return self.session.get(Paths.PRIORITIES_ID.format(priority_id)) + + def update_priority(self, priority_id: int, project_id: int, priority_name: str, + index: int, default: bool, **kwargs): + """ + Update an existing priority. Please take into account that for each non-specified field + the value will be cleared. + + :param priority_id: Priority ID + :param project_id: Project ID + :param priority_name: The priority name + :param index: The priority index + :param default: The priority default flag + :return: dict with response body + """ + json = {"id": priority_id, + "project": {"id": project_id}, + "name": priority_name, + "index": index, + "default": default} + json.update(kwargs) + return self.session.put(Paths.PRIORITIES_ID.format(priority_id), json=json) diff --git a/zephyr/scale/cloud/endpoints/priorities.py b/zephyr/scale/cloud/endpoints/priorities.py deleted file mode 100644 index c73c86d..0000000 --- a/zephyr/scale/cloud/endpoints/priorities.py +++ /dev/null @@ -1,16 +0,0 @@ -from ...zephyr_session import ZephyrSession - - -class PriorityEndpoints: - """Api wrapper for "Priority" endpoints""" - - def __init__(self, session: ZephyrSession): - self.session = session - - def get_priorities(self, **kwargs): - """Returns all priorities""" - return self.session.get_paginated("priorities", params=kwargs) - - def get_priority(self, priority_id): - """Returns a priority for the given ID""" - return self.session.get(f"priorities/{priority_id}") From 5132e5d3fec45d661ef9c2e829707aa75c0bf5f6 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 21 Apr 2024 21:23:49 +0200 Subject: [PATCH 29/37] #23: Move EnvironmentEndpoints,ProjectEndpoints,LinkEndpoints,AutomationEndpoints,HealthcheckEndpoints to endpoints.py --- zephyr/scale/cloud/endpoints/__init__.py | 13 +- zephyr/scale/cloud/endpoints/automations.py | 116 ------------ zephyr/scale/cloud/endpoints/endpoints.py | 176 +++++++++++++++++++ zephyr/scale/cloud/endpoints/environments.py | 16 -- zephyr/scale/cloud/endpoints/healthcheck.py | 16 -- zephyr/scale/cloud/endpoints/links.py | 18 -- zephyr/scale/cloud/endpoints/projects.py | 16 -- 7 files changed, 183 insertions(+), 188 deletions(-) delete mode 100644 zephyr/scale/cloud/endpoints/automations.py delete mode 100644 zephyr/scale/cloud/endpoints/environments.py delete mode 100644 zephyr/scale/cloud/endpoints/healthcheck.py delete mode 100644 zephyr/scale/cloud/endpoints/links.py delete mode 100644 zephyr/scale/cloud/endpoints/projects.py diff --git a/zephyr/scale/cloud/endpoints/__init__.py b/zephyr/scale/cloud/endpoints/__init__.py index 6fa2590..5c29f02 100644 --- a/zephyr/scale/cloud/endpoints/__init__.py +++ b/zephyr/scale/cloud/endpoints/__init__.py @@ -4,9 +4,10 @@ TestExecutionEndpoints, FolderEndpoints, StatusEndpoints, - PriorityEndpoints) -from .environments import EnvironmentEndpoints -from .projects import ProjectEndpoints -from .links import LinkEndpoints -from .healthcheck import HealthcheckEndpoints -from .automations import AutomationEndpoints + PriorityEndpoints, + EnvironmentEndpoints, + ProjectEndpoints, + LinkEndpoints, + IssueLinksEndpoints, + AutomationEndpoints, + HealthcheckEndpoints) diff --git a/zephyr/scale/cloud/endpoints/automations.py b/zephyr/scale/cloud/endpoints/automations.py deleted file mode 100644 index 093a985..0000000 --- a/zephyr/scale/cloud/endpoints/automations.py +++ /dev/null @@ -1,116 +0,0 @@ -from json import dumps - -from ...zephyr_session import ZephyrSession - - -class AutomationEndpoints: - """Api wrapper for "Automation" endpoints""" - - def __init__(self, session: ZephyrSession): - self.session = session - - def _post_reports(self, - path, - project_key, - file_path, - auto_create=False, - test_cycle=None, - **kwargs): - """ - Post various reports logic. - - :param path: str with resource path - :param project_key: str with project key - :param file_path: str with path to .zip archive with report files - :param auto_create: indicate if test cases should be created if non existent - :param test_cycle: dict with test cycle description data - """ - params = {'projectKey': project_key} - to_files = None - - if auto_create: - params.update({'autoCreateTestCases': True}) - - if test_cycle: - to_files = {'testCycle': (None, dumps(test_cycle), 'application/json')} - - return self.session.post_file(path, - file_path, - to_files=to_files, - params=params, - **kwargs) - - def post_custom_format(self, - project_key, - file_path, - auto_create=False, - test_cycle=None, - **kwargs): - """ - Create results using Zephyr Scale's custom results format. - - :param project_key: str with project key - :param file_path: str with path to .zip archive with report files - :param auto_create: indicate if test cases should be created if non existent - :param test_cycle: dict with test cycle description data - """ - return self._post_reports('automations/executions/custom', - project_key=project_key, - file_path=file_path, - auto_create=auto_create, - test_cycle=test_cycle, - **kwargs) - - def post_cucumber_format(self, - project_key, - file_path, - auto_create=False, - test_cycle=None, - **kwargs): - """ - Create results using the Cucumber results format. - - :param project_key: str with project key - :param file_path: str with path to .zip archive with report files - :param auto_create: indicate if test cases should be created if non existent - :param test_cycle: dict with test cycle description data - """ - return self._post_reports('automations/executions/cucumber', - project_key=project_key, - file_path=file_path, - auto_create=auto_create, - test_cycle=test_cycle, - **kwargs) - - def post_junit_xml_format(self, - project_key, - file_path, - auto_create=False, - test_cycle=None, - **kwargs): - """ - Create results using the JUnit XML results format. - - :param project_key: str with project key - :param file_path: str with path to .zip archive with report files - :param auto_create: indicate if test cases should be created if non existent - :param test_cycle: dict with test cycle description data - """ - return self._post_reports('automations/executions/junit', - project_key=project_key, - file_path=file_path, - auto_create=auto_create, - test_cycle=test_cycle, - **kwargs) - - def get_testcases_zip(self, project_key): - """ - Retrieve a zip file containing Cucumber Feature Files that matches - the query passed as parameter. - """ - params = {"projectKey": project_key} - headers = {"Accept": "application/zip"} - return self.session.get("automations/testcases", - return_raw=True, - params=params, - headers=headers) diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py index e80772b..6f2db1a 100644 --- a/zephyr/scale/cloud/endpoints/endpoints.py +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -1,3 +1,4 @@ +from json import dumps from typing import Union from ...zephyr_session import EndpointTemplate @@ -539,3 +540,178 @@ def update_priority(self, priority_id: int, project_id: int, priority_name: str, "default": default} json.update(kwargs) return self.session.put(Paths.PRIORITIES_ID.format(priority_id), json=json) + + +class EnvironmentEndpoints(EndpointTemplate): + """Api wrapper for "Environment" endpoints""" + + def get_environments(self, **kwargs): + """Returns all environments""" + return self.session.get_paginated(Paths.ENVIRONMENTS, params=kwargs) + + def get_environment(self, environment_id: int): + """ + Returns an environment for the given ID + + :param environment_id: Environment ID + :return: dict with response body + """ + return self.session.get(Paths.ENVIRONMENTS_ID.format(environment_id)) + + +class ProjectEndpoints(EndpointTemplate): + """Api wrapper for "Project" endpoints""" + + def get_projects(self): + """Returns all projects""" + return self.session.get_paginated(Paths.PROJECTS) + + def get_project(self, project_id_or_key: Union[str, int]): + """ + Returns a project for the given ID or key + + :param project_id_or_key: The ID or key of the Jira project + """ + return self.session.get(Paths.PROJECTS_KEY.format(project_id_or_key)) + + +class LinkEndpoints(EndpointTemplate): + """Api wrapper for "Link" endpoints""" + + def delete_link(self, link_id: int): + """Deletes a link for the given ID. + + :param link_id: The id of a link to delete + :return: dict with response body + """ + return self.session.delete(Paths.LINKS_ID.format(link_id)) + + +class IssueLinksEndpoints(EndpointTemplate): + """Api wrapper for "Issue Links" endpoints""" + + pass + + +class AutomationEndpoints(EndpointTemplate): + """ + Api wrapper for "Automation" endpoints. + + Operations related to test case automations. + """ + + def _post_reports(self, + path, + project_key, + file_path, + auto_create=False, + test_cycle=None, + **kwargs): + """ + Post various reports logic. + + :param path: str with resource path + :param project_key: str with project key + :param file_path: str with path to .zip archive with report files + :param auto_create: indicate if test cases should be created if non existent + :param test_cycle: dict with test cycle description data + """ + params = {'projectKey': project_key} + to_files = None + + if auto_create: + params.update({'autoCreateTestCases': True}) + + if test_cycle: + to_files = {'testCycle': (None, dumps(test_cycle), 'application/json')} + + return self.session.post_file(path, + file_path, + to_files=to_files, + params=params, + **kwargs) + + def post_custom_format(self, + project_key, + file_path, + auto_create=False, + test_cycle=None, + **kwargs): + """ + Create results using Zephyr Scale's custom results format. + + :param project_key: str with project key + :param file_path: str with path to .zip archive with report files + :param auto_create: indicate if test cases should be created if non existent + :param test_cycle: dict with test cycle description data + """ + return self._post_reports(Paths.AUT_CUSTOM, + project_key=project_key, + file_path=file_path, + auto_create=auto_create, + test_cycle=test_cycle, + **kwargs) + + def post_cucumber_format(self, + project_key, + file_path, + auto_create=False, + test_cycle=None, + **kwargs): + """ + Create results using the Cucumber results format. + + :param project_key: str with project key + :param file_path: str with path to .zip archive with report files + :param auto_create: indicate if test cases should be created if non existent + :param test_cycle: dict with test cycle description data + """ + return self._post_reports(Paths.AUT_CUCUMBER, + project_key=project_key, + file_path=file_path, + auto_create=auto_create, + test_cycle=test_cycle, + **kwargs) + + def post_junit_xml_format(self, + project_key, + file_path, + auto_create=False, + test_cycle=None, + **kwargs): + """ + Create results using the JUnit XML results format. + + :param project_key: str with project key + :param file_path: str with path to .zip archive with report files + :param auto_create: indicate if test cases should be created if non existent + :param test_cycle: dict with test cycle description data + """ + return self._post_reports(Paths.AUT_JUNIT, + project_key=project_key, + file_path=file_path, + auto_create=auto_create, + test_cycle=test_cycle, + **kwargs) + + def get_testcases_zip(self, project_key: str): + """ + Retrieve a zip file containing Cucumber Feature Files that matches + the query passed as parameter. + + :param project_key: Jira project key filter + """ + params = {"projectKey": project_key} + headers = {"Accept": "application/zip"} + return self.session.get(Paths.AUT_CASES, + return_raw=True, + params=params, + headers=headers) + + +class HealthcheckEndpoints(EndpointTemplate): + """Api wrapper for "Healthcheck" endpoints""" + + def get_health(self): + """Check the health of this API.""" + return self.session.get(Paths.HEALTHCHECK) diff --git a/zephyr/scale/cloud/endpoints/environments.py b/zephyr/scale/cloud/endpoints/environments.py deleted file mode 100644 index 8dfa029..0000000 --- a/zephyr/scale/cloud/endpoints/environments.py +++ /dev/null @@ -1,16 +0,0 @@ -from ...zephyr_session import ZephyrSession - - -class EnvironmentEndpoints: - """Api wrapper for "Environment" endpoints""" - - def __init__(self, session: ZephyrSession): - self.session = session - - def get_environments(self, **kwargs): - """Returns all environments""" - return self.session.get_paginated("environments", params=kwargs) - - def get_environment(self, environment_id): - """Returns an environment for the given ID""" - return self.session.get(f"environments/{environment_id}") diff --git a/zephyr/scale/cloud/endpoints/healthcheck.py b/zephyr/scale/cloud/endpoints/healthcheck.py deleted file mode 100644 index f6ab761..0000000 --- a/zephyr/scale/cloud/endpoints/healthcheck.py +++ /dev/null @@ -1,16 +0,0 @@ -from ...zephyr_session import ZephyrSession - - -class HealthcheckEndpoints: - """Api wrapper for "Healthcheck" endpoints""" - - def __init__(self, session: ZephyrSession): - self.session = session - - def get_health(self): - """Check the health of this API - - :return: health check response body - :rtype: dict - """ - return self.session.get("healthcheck") diff --git a/zephyr/scale/cloud/endpoints/links.py b/zephyr/scale/cloud/endpoints/links.py deleted file mode 100644 index 1775b91..0000000 --- a/zephyr/scale/cloud/endpoints/links.py +++ /dev/null @@ -1,18 +0,0 @@ -from ...zephyr_session import ZephyrSession - - -class LinkEndpoints: - """Api wrapper for "Link" endpoints""" - - def __init__(self, session: ZephyrSession): - self.session = session - - def delete_link(self, link_id): - """Deletes a link for the given ID. - - :param link_id: The id of a link to delete - :type link_id: int - :return: response body - :rtype: dict - """ - return self.session.delete(f"links/{link_id}") diff --git a/zephyr/scale/cloud/endpoints/projects.py b/zephyr/scale/cloud/endpoints/projects.py deleted file mode 100644 index 5edc8a6..0000000 --- a/zephyr/scale/cloud/endpoints/projects.py +++ /dev/null @@ -1,16 +0,0 @@ -from ...zephyr_session import ZephyrSession - - -class ProjectEndpoints: - """Api wrapper for "Project" endpoints""" - - def __init__(self, session: ZephyrSession): - self.session = session - - def get_projects(self): - """Returns all projects""" - return self.session.get_paginated("projects") - - def get_project(self, project_id_or_key): - """Returns a project for the given ID or key""" - return self.session.get(f"projects/{project_id_or_key}") From 9645b05977e002c68b2bb9abb13aa731d11c3d07 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 21 Apr 2024 21:30:17 +0200 Subject: [PATCH 30/37] #23: Add methods to IssueLinksEndpoints in Cloud --- zephyr/scale/cloud/endpoints/endpoints.py | 42 +++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py index 6f2db1a..b09da82 100644 --- a/zephyr/scale/cloud/endpoints/endpoints.py +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -588,9 +588,47 @@ def delete_link(self, link_id: int): class IssueLinksEndpoints(EndpointTemplate): - """Api wrapper for "Issue Links" endpoints""" + """ + Api wrapper for "Issue Links" endpoints + + Operations related to coverage of issue links. + """ + + def get_test_cases(self, issue_key: str): + """ + Get test case keys and versions linked to the given Jira issue. + + :param issue_key: The key of the Jira issue + :return: dict with response body + """ + return self.session.get(Paths.ISLINKS_CASES.format(issue_key)) + + def get_test_cycles(self, issue_key: str): + """ + Get test cycle IDs linked to the given Jira issue. + + :param issue_key: The key of the Jira issue + :return: dict with response body + """ + return self.session.get(Paths.ISLINKS_CYCLES.format(issue_key)) + + def get_test_plans(self, issue_key: str): + """ + Get test plan IDs linked to the given Jira issue. - pass + :param issue_key: The key of the Jira issue + :return: dict with response body + """ + return self.session.get(Paths.ISLINKS_PLANS.format(issue_key)) + + def get_test_executions(self, issue_key: str): + """ + Get test execution IDs linked to the given Jira issue. + + :param issue_key: The key of the Jira issue + :return: dict with response body + """ + return self.session.get(Paths.ISLINKS_EXECS.format(issue_key)) class AutomationEndpoints(EndpointTemplate): From f3843ab8f049215959008fbc567d4b3380d4c361 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 21 Apr 2024 22:31:55 +0200 Subject: [PATCH 31/37] #23: Add IssueLinksEndpoints to CloudApiWrapper --- zephyr/scale/cloud/cloud_api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zephyr/scale/cloud/cloud_api.py b/zephyr/scale/cloud/cloud_api.py index 3c9e103..1fcd2a8 100644 --- a/zephyr/scale/cloud/cloud_api.py +++ b/zephyr/scale/cloud/cloud_api.py @@ -63,6 +63,10 @@ def projects(self): def links(self): return endpoints.LinkEndpoints(self.session) + @property + def issue_links(self): + return endpoints.IssueLinksEndpoints(self.session) + @property def automations(self): return endpoints.AutomationEndpoints(self.session) From 16bb3b1791e6ecbc27c3abd79cc25343803a77b8 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sat, 27 Apr 2024 23:08:47 +0200 Subject: [PATCH 32/37] #23: Minor fixes for cloud api docstrings --- zephyr/scale/cloud/endpoints/endpoints.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py index b09da82..3a10da5 100644 --- a/zephyr/scale/cloud/endpoints/endpoints.py +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -70,7 +70,7 @@ def update_test_case(self, :param test_case_key: The key of the test case :param test_case_id: integer id of the test - :param name: test case nae + :param name: test case name :param project_id: project id :param priority_id: priority id :param status_id: status id @@ -407,7 +407,7 @@ class FolderEndpoints(EndpointTemplate): def get_folders(self, **kwargs): """ - Returns all folder. + Returns all folders. """ return self.session.get_paginated(Paths.FOLDERS, params=kwargs) From 6e6238c26ec219f45cf2160c94a6dc1e59ecfb32 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sat, 27 Apr 2024 23:09:46 +0200 Subject: [PATCH 33/37] #23: Fix url formatting in update_test_cycle --- zephyr/scale/cloud/endpoints/endpoints.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py index 3a10da5..c53c66d 100644 --- a/zephyr/scale/cloud/endpoints/endpoints.py +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -218,7 +218,7 @@ def update_test_cycle(self, "project": {"id": project_id}, "status": {"id": status_id}} json.update(kwargs) - return self.session.put(Paths.CYCLE_KEY, json=json) + return self.session.put(Paths.CYCLE_KEY.format(test_cycle_key), json=json) def get_links(self, test_cycle_id_or_key: Union[str, int]): """ From d967155ea129f65cc1daedb8c2f59ddfa9724a4e Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 28 Apr 2024 10:36:12 +0200 Subject: [PATCH 34/37] #23: Fix pylint errors in endpoints.py --- zephyr/scale/cloud/endpoints/endpoints.py | 25 +++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/zephyr/scale/cloud/endpoints/endpoints.py b/zephyr/scale/cloud/endpoints/endpoints.py index c53c66d..7e85857 100644 --- a/zephyr/scale/cloud/endpoints/endpoints.py +++ b/zephyr/scale/cloud/endpoints/endpoints.py @@ -172,7 +172,8 @@ def get_test_cycles(self, **kwargs): return self.session.get_paginated(Paths.CYCLES, params=kwargs) def create_test_cycle(self, project_key: str, name: str, **kwargs): - """Creates a test cycle. All required test cycle custom fields should be present in the request. + """Creates a test cycle. All required test cycle custom fields + should be present in the request. :param project_key: Jira project key :param name: test cycle name @@ -269,7 +270,8 @@ def get_test_plans(self, **kwargs): return self.session.get_paginated(Paths.PLANS, params=kwargs) def create_test_plan(self, project_key: str, name: str, **kwargs): - """Creates a test plan. All required test plan custom fields should be present in the request. + """Creates a test plan. All required test plan custom fields + should be present in the request. :param project_key: Jira project key :param name: test plan name @@ -284,7 +286,7 @@ def get_test_plan(self, test_plan_key: Union[str, int]): """ Returns a test plan for the given id or key. - :param test_plan_key: The ID or key of the test plan. Test plan keys are of the format [A-Z]+-P[0-9]+ + :param test_plan_key: The ID or key of the test plan :return: dict with response body """ return self.session.get(Paths.PLAN_KEY.format(test_plan_key)) @@ -293,7 +295,7 @@ def create_web_links(self, test_plan_key: Union[str, int], url: str, description """ Creates a link between a test plan and a generic URL. - :param test_plan_key: The ID or key of the test plan. Test plan keys are of the format [A-Z]+-P[0-9]+ + :param test_plan_key: The ID or key of the test plan :param url: The web link URL :param description: The link description :return: dict with response body @@ -306,7 +308,7 @@ def create_issue_link(self, test_plan_key: Union[str, int], issue_id: int): """ Creates a link between a test plan and a Jira issue. - :param test_plan_key: The ID or key of the test plan. Test plan keys are of the format [A-Z]+-P[0-9]+ + :param test_plan_key: The ID or key of the test plan :param issue_id: The issue ID :return: dict with response body """ @@ -317,8 +319,8 @@ def create_test_cycle_link(self, test_plan_key: Union[str, int], test_cycle_id: """ Creates a link between a test plan and a test cycle. - :param test_plan_key: The ID or key of the test plan. Test plan keys are of the format [A-Z]+-P[0-9]+ - :param test_cycle_id: The ID or key of the test cycle. Test cycle keys are of the format [A-Z]+-R[0-9]+ + :param test_plan_key: The ID or key of the test plan + :param test_cycle_id: The ID or key of the test cycle :return: dict with response body """ return self.session.post(Paths.PLAN_CYCLES.format(test_plan_key), @@ -356,11 +358,11 @@ def get_test_execution(self, test_execution_id_or_key: Union[str, int], **kwargs """ Returns a test execution for the given ID - :param test_execution_id_or_key: The ID or key of the test execution. - Test execution keys are of the format [A-Z]+-E[0-9]+ + :param test_execution_id_or_key: The ID or key of the test execution :return: dict with response body """ - return self.session.get(Paths.EXECUTIONS_KEY.format(test_execution_id_or_key), params=kwargs) + return self.session.get(Paths.EXECUTIONS_KEY.format(test_execution_id_or_key), + params=kwargs) def update_test_execution(self, test_execution_id_or_key: Union[str, int], **kwargs): """ @@ -449,7 +451,8 @@ def create_status(self, project_key: str, status_name: str, status_type: str, ** :param project_key: Jira project key :param status_name: The status name - :param status_type: The status type. Valid values: "TEST_CASE", "TEST_PLAN", "TEST_CYCLE", "TEST_EXECUTION" + :param status_type: The status type. + Valid values: "TEST_CASE", "TEST_PLAN", "TEST_CYCLE", "TEST_EXECUTION" :return: dict with response body """ json = {"projectKey": project_key, From cb7858ff2ecee8edf2d8b5118627bf11ac13726a Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Sun, 28 Apr 2024 13:16:20 +0200 Subject: [PATCH 35/37] Bump version to 0.1.0 --- VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION.txt b/VERSION.txt index bbdeab6..6e8bf73 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.0.5 +0.1.0 From ad8d483934dbe55fa3ac1bdc31f048a213b74395 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Thu, 2 May 2024 17:58:53 +0200 Subject: [PATCH 36/37] Update description of the project in the index.rst --- docs/source/index.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index acb97b8..42863bf 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -7,12 +7,12 @@ Welcome to Zephyr Python API's documentation! ============================================= The package is a set of python wrappers for Zephyr Scale (TM4J) REST API (both Cloud and Server/DataCenter). -Interact with Zephyr Scale without GUI, access it with python code and create automation scripts for your daily routines. +Interact with Zephyr Scale using python and create automation scripts for your daily routines. + +The idea of the package is to have two parts in it: a set of low-level wrappers and Zephyr objects (like a test case or a test cycle). +The low-level wrappers simply perform requests to the API endpoints. +The objects should provide OOP approach and leverage the low-level wrappers. Currently the Zephyr objects are not implemented. -The idea of the package is to have two parts in it: a set of low-level wrappers and Zephyr objects (like a test case or a test cycle). -The low-level wrappers are simply performing requests to the API endpoints of Zephyr with no logic added. The Zephyr objects -is a set of classes where the Zephyr interaction logic is placed. The logic is implemented using the low-level API wrappers. -Currently the Zephyr objects are not implemented. .. toctree:: :maxdepth: 3 From b129d3caa10e0a57f2c051d706e950b1dec15ce5 Mon Sep 17 00:00:00 2001 From: Petr Sharapenko Date: Thu, 2 May 2024 18:03:36 +0200 Subject: [PATCH 37/37] Update version in docs --- docs/source/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index eec89f2..2196e0d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -12,9 +12,9 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = 'Zephyr Python API' -copyright = '2023, Petr Sharapenko' +copyright = '2024, Petr Sharapenko' author = 'Petr Sharapenko' -release = '0.0.4' +release = '0.1.0' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration