├── example ├── PyGreat.png ├── keywords_ExampleDefs.xml ├── Last7DaysTestCases.py ├── all-req.xml ├── customFields_ExampleDefs.xml ├── TestLinkExample_CF_KW.py └── TestLinkExampleGenericApi_Req.py ├── .gitignore ├── .env ├── MANIFEST.in ├── .vscode ├── settings.json └── tasks.json ├── src └── testlink │ ├── version.py │ ├── __init__.py │ ├── proxiedtransport2.py │ ├── proxiedtransport3.py │ ├── testlinkerrors.py │ ├── testgenreporter.py │ ├── testlinkargs.py │ ├── testlinkhelper.py │ ├── testlinkdecorators.py │ └── testreporter.py ├── tox.ini ├── test ├── utest-offline │ ├── test_py3_vs_py2.py │ ├── testlinkargs_test.py │ ├── test_apiClients_whatArgs.py │ ├── testlinkhelper_test.py │ └── testlinkdecorators_test.py ├── utest-online │ ├── testlinkapi_callserver_test.py │ └── test_apiCall_differentPositionalArgs.py └── conftest.py ├── robot ├── tlweb_resource.robot ├── tlapi.robot ├── TestlinkAPILibrary.py ├── tlweb.robot └── TestlinkSeLibExtension.py ├── setup.py ├── doc ├── install.rst ├── usage.rst └── fr_usage.rst ├── README.rst └── LICENSE-2.0.txt /example/PyGreat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lczub/TestLink-API-Python-client/HEAD/example/PyGreat.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .tox 2 | .idea 3 | .project 4 | .pydevproject 5 | .settings/ 6 | build/ 7 | *.pyc 8 | dist/ 9 | MANIFEST 10 | .pytest_cache/ 11 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PYTHONPATH="./src" 2 | TESTLINK_API_PYTHON_SERVER_URL="http://localhost:8085/lib/api/xmlrpc/v1/xmlrpc.php" 3 | TESTLINK_API_PYTHON_DEVKEY="48072c25257af9f477a22c97a3858337" 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in LICENSE-2.0.txt README.rst CHANGES.rst tox.ini 2 | recursive-include src *.py 3 | recursive-include example *.py 4 | recursive-include example *.png 5 | recursive-include example *.xml 6 | recursive-include test *.py 7 | recursive-include doc *.rst 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.autoComplete.extraPaths": [ 3 | "${workspaceRoot}/src" 4 | ], 5 | "python.testing.pytestArgs": [ 6 | "test" 7 | ], 8 | "python.testing.unittestEnabled": false, 9 | "python.testing.pytestEnabled": true 10 | 11 | } -------------------------------------------------------------------------------- /example/keywords_ExampleDefs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/testlink/version.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2013-2021 TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | VERSION = '0.8.2-dev141' 21 | TL_RELEASE = '1.9.20-fixed_c88e348ce' 22 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # content of: tox.ini , put in same dir as setup.py 2 | # default target for online test - testlink demo application with user pyTLapi 3 | # - to use your own local testlink application, define environment variables 4 | # TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_SERVER_URL 5 | # with your server settings 6 | [tox] 7 | envlist = py27,py36,py37 8 | [testenv] 9 | deps=pytest 10 | setenv= 11 | TESTLINK_API_PYTHON_DEVKEY={env:TESTLINK_API_PYTHON_DEVKEY:3ec96581bb3d8a34cd28ce338b641063} 12 | TESTLINK_API_PYTHON_SERVER_URL={env:TESTLINK_API_PYTHON_SERVER_URL:http://demo.testlink.org/latest/lib/api/xmlrpc/v1/xmlrpc.php} 13 | commands= 14 | py.test --junitxml={envlogdir}/pyunit-offline-{envname}.xml test/utest-offline 15 | python -m testlink.testlinkapi # prompt infos about testlink connection 16 | py.test --junitxml={envlogdir}/pyunit-online-{envname}.xml test/utest-online 17 | python example\TestLinkExampleGenericApi.py 18 | python example\TestLinkExample.py 19 | 20 | -------------------------------------------------------------------------------- /src/testlink/__init__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2012-2019 TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | 21 | from .testlinkerrors import TestLinkError 22 | from .testlinkapigeneric import TestlinkAPIGeneric 23 | from .testlinkapi import TestlinkAPIClient 24 | from .testlinkhelper import TestLinkHelper 25 | from .testreporter import TestReporter 26 | from .testgenreporter import TestGenReporter 27 | -------------------------------------------------------------------------------- /test/utest-offline/test_py3_vs_py2.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2018-2020 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | # TestCases for Testlink API clients handling py2 and py3 differences 21 | # - TestlinkAPIClient, TestlinkAPIGeneric 22 | # 23 | 24 | import pytest 25 | #from conftest import api_general_client, api_generic_client 26 | 27 | def test_IS_PY3_same_state(): 28 | from testlink.testlinkhelper import IS_PY3 as proxie_is_py3 29 | from testlink.testlinkapigeneric import IS_PY3 as tl_is_py3 30 | assert proxie_is_py3 == tl_is_py3 31 | 32 | -------------------------------------------------------------------------------- /robot/tlweb_resource.robot: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2019 Luiko Czub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | *** Settings *** 16 | Documentation Resource file for testing TestLink XMLRPC api with web app interaction 17 | Library Selenium2Library 18 | Library TestlinkSeLibExtension ${TL_URL} ${TL_USER} ${TL_PASSWORD} 19 | 20 | *** Variables *** 21 | ${SERVER} lkatlinkd7/testlink 22 | ${BROWSER} ff 23 | ${DELAY} 0.1 24 | ${TL_USER} admin 25 | ${TL_PASSWORD} admin 26 | ${TL_URL} http://${SERVER}/ 27 | 28 | *** Keywords *** 29 | Open Browser with TestLink Page 30 | [Documentation] prepares the browser for testing the TestLink web app 31 | Open Browser ${TL_URL} ${BROWSER} 32 | Maximize Browser Window 33 | Set Selenium Speed ${DELAY} 34 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "echo", 8 | "type": "shell", 9 | "command": "echo Hello" 10 | }, 11 | { 12 | "label": "test Testlink Api sample", 13 | "type": "process", 14 | "detail": "Run sample Testlink Api Python Client communication", 15 | "command": "${config:python.defaultInterpreterPath}", 16 | "args": [ "${workspaceFolder}/example/${input:apiSample}.py" ], 17 | "group": "test", 18 | "presentation": { 19 | "clear": true, 20 | "panel": "dedicated" 21 | }, 22 | "problemMatcher": ["$python"], 23 | "options": { 24 | "cwd": "${workspaceFolder}", 25 | "env": { "PYTHONPATH" : "./src", 26 | "TESTLINK_API_PYTHON_SERVER_URL" : "http://localhost:8085/lib/api/xmlrpc/v1/xmlrpc.php", 27 | "TESTLINK_API_PYTHON_DEVKEY" : "48072c25257af9f477a22c97a3858337" 28 | } 29 | } 30 | } 31 | ], 32 | "inputs": [ 33 | { 34 | "type": "pickString", 35 | "id": "apiSample", 36 | "description": "Which TL API sample to run ?", 37 | "options": ["TestLinkExample", "TestLinkExampleGenericApi","TestLinkExample_CF_KW","TestLinkExampleGenericApi_Req"], 38 | "default": "TestLinkExample" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /src/testlink/proxiedtransport2.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2014-2020 Mario Benito, Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | # XMLRPC ProxiedTransport for Py27 as described in 20 | # https://docs.python.org/2.7/library/xmlrpclib.html#example-of-client-usage 21 | # ------------------------------------------------------------------------ 22 | 23 | import xmlrpclib, httplib 24 | 25 | class ProxiedTransport(xmlrpclib.Transport): 26 | ''' XMLRPC ProxiedTransport for Py27 as described in 27 | https://docs.python.org/2.7/library/xmlrpclib.html#example-of-client-usage 28 | ''' 29 | 30 | def set_proxy(self, proxy): 31 | self.proxy = proxy 32 | 33 | def make_connection(self, host): 34 | self.realhost = host 35 | h = httplib.HTTPConnection(self.proxy) 36 | return h 37 | 38 | def send_request(self, connection, handler, request_body): 39 | connection.putrequest("POST", 'http://%s%s' % (self.realhost, handler)) 40 | 41 | def send_host(self, connection, host): 42 | connection.putheader('Host', self.realhost) 43 | 44 | -------------------------------------------------------------------------------- /src/testlink/proxiedtransport3.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2020 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | from http.client import HTTPConnection 21 | from xmlrpc.client import Transport 22 | from urllib.parse import urlparse, urlunparse 23 | 24 | class ProxiedTransport(Transport): 25 | ''' XMLRPC ProxiedTransport for Py37+ as described in 26 | https://docs.python.org/3.8/library/xmlrpc.client.html#example-of-client-usage 27 | ''' 28 | 29 | def set_proxy(self, host, port=None, headers=None): 30 | ''' if host includes a port definition (e.g. http://myHost:1111) 31 | this will be used instead the optional PORT arg 32 | ''' 33 | 34 | u1 = urlparse(host) 35 | uport = u1.port 36 | u2 = u1._replace(netloc=u1.hostname) 37 | uhost = urlunparse(u2) 38 | 39 | self.proxy = uhost, uport or port 40 | self.proxy_headers = headers 41 | 42 | def make_connection(self, host): 43 | connection = HTTPConnection(*self.proxy) 44 | connection.set_tunnel(host, headers=self.proxy_headers) 45 | self._connection = host, connection 46 | return connection 47 | -------------------------------------------------------------------------------- /src/testlink/testlinkerrors.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2012-2019 Patrick Dassier, Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | class TestLinkError(Exception): 21 | """ Basic error 22 | Return message pass as argument 23 | """ 24 | # def __init__(self, msg): 25 | # self.__msg = msg 26 | # 27 | # def __str__(self): 28 | # return self.__msg 29 | 30 | class TLConnectionError(TestLinkError): 31 | """ Connection error 32 | - wrong url? - server not reachable? """ 33 | 34 | class TLAPIError(TestLinkError): 35 | """ API error 36 | - wrong method name ? - misssing required args? """ 37 | 38 | class TLArgError(TestLinkError): 39 | """ Call error 40 | - wrong number of mandatory params ? - wrong param type? """ 41 | 42 | class TLResponseError(TestLinkError): 43 | """ Response error 44 | - Response is empty or includes error codes """ 45 | 46 | def __init__(self, methodNameAPI, argsOptional, message, code=None): 47 | self.methodNameAPI = methodNameAPI 48 | self.argsOptional = argsOptional 49 | self.message = message 50 | self.code = code 51 | msg = '%s\n%s(%s)' % (message, methodNameAPI, argsOptional) 52 | if code: 53 | msg = '%s: %s' % (code, msg) 54 | return super(TLResponseError, self).__init__(msg) 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /example/Last7DaysTestCases.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2011-2019 Olivier Renault, James Stock, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | # This example shows, how the API could be used to list all test cases, 21 | # which have been created during the last 7 days 22 | 23 | 24 | from __future__ import print_function 25 | from testlink import TestlinkAPIClient, TestLinkHelper 26 | import time 27 | 28 | 29 | def iterTCasesfromTProject(api, TProjName, date1, date2): 30 | """ returns as iterator all test cases of project TPROJTNAME, which are 31 | created between DATE1 and DATE2 32 | DATE1 and DATE2 must be of type time.struct_time """ 33 | TProjId = api.getTestProjectByName(TProjName)['id'] 34 | for TSinfo in api.getFirstLevelTestSuitesForTestProject(TProjId): 35 | TSuiteId = TSinfo['id'] 36 | for TCid in api.getTestCasesForTestSuite(TSuiteId, deep=1,details='only_id'): 37 | TCdata = api.getTestCase(TCid)[0] #really only one TC? 38 | dateTC=time.strptime(TCdata['creation_ts'][:10], '%Y-%m-%d') 39 | if (date1 <= dateTC) and (dateTC <= date2): 40 | yield TCdata 41 | 42 | 43 | if __name__ == '__main__': 44 | tlapi = TestLinkHelper().connect(TestlinkAPIClient) 45 | projName = 'NEW_PROJECT_API' 46 | currentTime = time.localtime() 47 | oldTime = time.localtime(time.time() - 3600 * 24 * 7) 48 | 49 | print('%s test cases created between %s and %s' % \ 50 | (projName, time.strftime('%Y-%m-%d', oldTime), 51 | time.strftime('%Y-%m-%d', currentTime))) 52 | for TCdata in iterTCasesfromTProject(tlapi, projName, oldTime, currentTime): 53 | print(' %(name)s %(version)s %(creation_ts)s' % TCdata) 54 | -------------------------------------------------------------------------------- /robot/tlapi.robot: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2019 Luiko Czub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | *** Settings *** 16 | Documentation Smoke tests for Library TestlinkAPILibrary 17 | ... 18 | ... TestlinkAPILibrary is a wrapper for calling 19 | ... Testlink XMLRPC api methods via *Testlink-API-Python-client* 20 | ... classes _TestlinkAPIClient_ and _TestlinkAPIGeneric_. 21 | Force Tags api 22 | Default Tags general 23 | Library TestlinkAPILibrary 24 | Library Collections 25 | 26 | *** Variables *** 27 | 28 | *** Test Cases *** 29 | Init Api Class TestLinkAPIClient 30 | ${tlapi_client}= Create Api Client TestlinkAPIClient 31 | ${method_args}= Call Method ${tlapi_client} whatArgs repeat 32 | Should Contain ${method_args} Repeats a message back 33 | Should Be Equal As Strings ${tlapi_client.__class__.__name__} TestlinkAPIClient 34 | 35 | Init Api Class TestlinkAPIGeneric 36 | ${tlapi_client}= Create Api Client TestlinkAPIGeneric 37 | ${method_args}= Call Method ${tlapi_client} whatArgs repeat 38 | Should Contain ${method_args} Repeats a message back 39 | Should Be Equal As Strings ${tlapi_client.__class__.__name__} TestlinkAPIGeneric 40 | 41 | Call Api Method without arguments 42 | ${response}= Call Api Method sayHello 43 | Should Be Equal As Strings ${response} Hello! 44 | 45 | Call Api Method with mandatory argument 46 | ${response}= Call Api Method repeat Hugo 47 | Should Be Equal As Strings ${response} You said: Hugo 48 | 49 | Call Api Method with optional arguments 50 | [Tags] devexample 51 | @{response1}= Call Api Method getTestCaseIDByName TESTCASE_B 52 | Should Not Be Empty ${response1} 53 | @{response2}= Call Api Method getTestCaseIDByName TESTCASE_B testprojectname=PROJECT_API_GENERIC-10 54 | Should Not Be Empty ${response2} 55 | -------------------------------------------------------------------------------- /test/utest-online/testlinkapi_callserver_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2012-2019 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | # this test requires an online TestLink Server, which connection parameters 21 | # are defined in environment variables 22 | # TESTLINK_API_PYTHON_SERVER_URL and TESTLINK_API_PYTHON_DEVKEY 23 | 24 | import pytest 25 | from testlink import testlinkerrors 26 | 27 | def test_callServer_noArgs(api_client): 28 | """ test _callServer() - calling method with no args """ 29 | 30 | assert 'Hello!' == api_client._callServer('sayHello') 31 | 32 | def test_callServer_withArgs(api_client): 33 | """ test _callServer() - calling method with args """ 34 | 35 | assert 'You said: some arg' == api_client._callServer('repeat', {'str': 'some arg'}) 36 | 37 | def test_callServer_ProtocolError(api_client_class, api_helper_class): 38 | """ test _callServer() - Server raises ProtocollError """ 39 | 40 | server_url = api_helper_class()._server_url 41 | bad_server_url = server_url.split('xmlrpc.php')[0] 42 | api_client = api_helper_class(bad_server_url).connect(api_client_class) 43 | 44 | with pytest.raises(testlinkerrors.TLConnectionError, 45 | match='ProtocolError'): 46 | api_client._callServer('sayHello') 47 | 48 | def test_callServer_socketError(api_client_class, api_helper_class): 49 | """ test _callServer() - Server raises a socket Error (getaddrinfo failed) """ 50 | 51 | bad_server_url = 'http://111.222.333.4/testlink/lib/api/xmlrpc.php' 52 | api_client = api_helper_class(bad_server_url).connect(api_client_class) 53 | 54 | with pytest.raises(testlinkerrors.TLConnectionError, 55 | match='getaddrinfo failed'): 56 | api_client._callServer('sayHello') 57 | 58 | def test_callServer_FaultError(api_client): 59 | """ test _callServer() - Server raises Fault Error """ 60 | 61 | with pytest.raises(testlinkerrors.TLAPIError): 62 | api_client._callServer('sayGoodBye') 63 | 64 | -------------------------------------------------------------------------------- /src/testlink/testgenreporter.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2017-2019 Brian-Willams, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | from .testreporter import AddTestCaseReporter, AddBuildReporter, AddTestPlanReporter, AddPlatformReporter, TestReporter 21 | 22 | 23 | class TestGenReporter(AddTestCaseReporter, AddBuildReporter, AddTestPlanReporter, AddPlatformReporter, TestReporter): 24 | """ 25 | This is the default generate everything it can version of test reporting. 26 | 27 | This class will always try to report a result. It will generate everything possible and will change with additional 28 | Add*Reporter's added to the repo. As such you should only use this if you want to always generate everything this 29 | repo is capable of. If you want what it does at any specific time you should create this class in your project and 30 | use directly. 31 | 32 | If you don't want to generate one of these values you can 'roll your own' version of this class with only the 33 | needed features that you want to generate. As stated above if you *only* want to generate what this class currently 34 | does. Copying it into your project is the best practice as this class is mutable inside the project! 35 | 36 | For example if you wanted to add platforms and/or tests to testplans, but didn't want to ever make a new testplan 37 | you could use a class like: 38 | `type('MyOrgTestGenReporter', (AddTestCaseReporter, AddPlatformReporter, TestReporter), {})` 39 | 40 | Example usage with fake testlink server test and a manual project. 41 | ``` 42 | tls = testlink.TestLinkHelper('https://testlink.corp.com/testlink/lib/api/xmlrpc/v1/xmlrpc.php', 43 | 'devkeyabc123').connect(testlink.TestlinkAPIClient) 44 | tgr = TestGenReporter(tls, ['TEST-123'], testprojectname='MANUALLY_MADE_PROJECT', testplanname='generated', 45 | platformname='gend', buildname='8.fake', status='p') 46 | tgr.report() 47 | ``` 48 | 49 | Attention - the list of test case IDs must use full external testcase IDs 50 | """ 51 | -------------------------------------------------------------------------------- /test/conftest.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2018-2019 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | import os.path 21 | import pytest 22 | from testlink import TestlinkAPIClient, TestlinkAPIGeneric, TestLinkHelper 23 | 24 | # example text file attachment = this python file 25 | # why not using os.path.realpath(__file__) 26 | # -> cause __file__ could be compiled python file *.pyc, if the test run is 27 | # repeated without changing the test code 28 | ATTACHMENT_EXAMPLE_TEXT= os.path.join(os.path.dirname(__file__), 29 | os.path.basename(__file__)) 30 | 31 | #attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') 32 | 33 | @pytest.fixture() 34 | def attachmentFile(): 35 | ''' open readonly attachment sample before test and close it afterwards ''' 36 | aFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r') 37 | yield aFile 38 | aFile.close() 39 | 40 | @pytest.fixture(scope='session') 41 | def api_helper_class(): 42 | return TestLinkHelper 43 | 44 | 45 | @pytest.fixture(scope='session') 46 | def api_generic_client(api_helper_class): 47 | ''' Init TestlinkAPIGeneric Client with connection parameters defined in 48 | environment variables 49 | TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY 50 | ''' 51 | return api_helper_class().connect(TestlinkAPIGeneric) 52 | 53 | @pytest.fixture(scope='session') 54 | def api_general_client(api_helper_class): 55 | ''' Init TestlinkAPIClient Client with connection parameters defined in 56 | environment variables 57 | TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY 58 | ''' 59 | return api_helper_class().connect(TestlinkAPIClient) 60 | 61 | @pytest.fixture(scope='session', params=[TestlinkAPIGeneric, TestlinkAPIClient]) 62 | def api_client_class(request): 63 | ''' all variations of Testlink API Client classes ''' 64 | return request.param 65 | 66 | @pytest.fixture(scope='session') 67 | def api_client(api_client_class, api_helper_class): 68 | ''' Init Testlink API Client class defined in fixtures api_client_class with 69 | connection parameters defined in environment variables 70 | TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY 71 | 72 | Tests will be call for each Testlink API Client class, defined in 73 | fixtures parameter list 74 | ''' 75 | return api_helper_class().connect(api_client_class) 76 | 77 | -------------------------------------------------------------------------------- /robot/TestlinkAPILibrary.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2014-2019 Luiko Czub 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | 21 | from testlink import TestlinkAPIGeneric, TestlinkAPIClient, TestLinkHelper 22 | 23 | 24 | class TestlinkAPILibrary(object): 25 | """Robot Framework test library for testing TestLink-API-Python-client, 26 | a Python XML-RPC client for TestLink. 27 | 28 | Best way to configure SERVER_URL and DEVKEY is to define them via enviroment 29 | TESTLINK_API_PYTHON_SERVER_URL and TESTLINK_API_PYTHON_DEVKEY from outside 30 | """ 31 | 32 | ROBOT_LIBRARY_SCOPE = 'TEST_SUITE' 33 | 34 | def __init__(self, client_class='TestlinkAPIClient', server_url=None, devkey=None ): 35 | self._client_class = client_class 36 | self._server_url = server_url 37 | self._devkey = devkey 38 | self.api_client = None 39 | 40 | def create_api_client(self, client_class='TestlinkAPIClient', 41 | server_url=None, devkey=None): 42 | """Creates a new TestLink-API-Python-client (XMLRPC), using Python class 43 | TestlinkAPIClient or TestlinkAPIGeneric. 44 | """ 45 | 46 | a_server_url = server_url or self._server_url 47 | a_devkey = devkey or self._devkey 48 | a_helper = TestLinkHelper(a_server_url, a_devkey) 49 | 50 | a_class_name = client_class or self._client_class 51 | a_api_class = globals()[a_class_name] 52 | self.api_client = a_helper.connect(a_api_class) 53 | return self.api_client 54 | 55 | def call_api_method(self, method_name, *args): 56 | """Calls a TestLink API method and returns TestLinks server response. 57 | """ 58 | 59 | # this is an extended version of the BuiltIn keyword "Call Method" 60 | 61 | client = self.api_client 62 | try: 63 | method = getattr(client, method_name) 64 | except AttributeError: 65 | raise RuntimeError("TLAPI Client '%s' does not have a method '%s'" 66 | % (client.__class__.__name__, method_name)) 67 | 68 | # split positional and optional args 69 | # condition optional args: argname=argvalue 70 | # FIXME LC: real ugly code 71 | posargs=[] 72 | optargs={} 73 | for a_arg in args: 74 | if '=' in a_arg: 75 | l = a_arg.split('=') 76 | optargs[l[0]]=l[1] 77 | else: 78 | posargs.append(a_arg) 79 | 80 | return method(*posargs, **optargs) 81 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2012-2019 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under Apache 2.0 7 | # 8 | 9 | from os.path import join, dirname 10 | from distutils.core import setup 11 | 12 | with open(join(dirname(__file__), 'src', 'testlink', 'version.py')) as fpv: 13 | version_code = compile(fpv.read(), 'version.py', 'exec') 14 | exec(version_code) 15 | 16 | CLASSIFIERS = [ 17 | 'Development Status :: 5 - Production/Stable', 18 | 'License :: OSI Approved :: Apache Software License', 19 | 'Operating System :: OS Independent', 20 | 'Programming Language :: Python :: 2.7', 21 | 'Programming Language :: Python :: 3.6', 22 | 'Programming Language :: Python :: 3.7', 23 | 'Topic :: Software Development :: Testing', 24 | 'Topic :: Software Development :: Libraries :: Python Modules' 25 | ] 26 | 27 | DESCRIPTION = """ 28 | TestLink-API-Python-client is a Python XML-RPC client for TestLink_. 29 | 30 | Initially based on James Stock testlink-api-python-client R7 and Olivier 31 | Renault JinFeng_ idea - an interaction of TestLink_, `Robot Framework`_ and Jenkins_. 32 | 33 | TestLink-API-Python-client delivers two main classes 34 | 35 | - TestlinkAPIGeneric - Implements the TestLink API methods as generic PY methods 36 | with error handling 37 | - TestlinkAPIClient - Inherits from TestlinkAPIGeneric and defines service 38 | methods like "copyTCnewVersion". 39 | 40 | and the helper class 41 | 42 | - TestLinkHelper - search connection parameter from environment variables and 43 | command line arguments 44 | 45 | How to talk with TestLink in a python shell and copy a test case: :: 46 | 47 | set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php 48 | set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink] 49 | python 50 | >>> import testlink 51 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 52 | >>> tls.countProjects() 53 | 3 54 | >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3') 55 | [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2', 56 | 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}] 57 | >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], testsuiteid=newSuiteID, 58 | testcasename='a new test case name') 59 | >>> print tls.whatArgs('createTestPlan') 60 | createTestPlan(, , [note=], [active=], 61 | [public=], [devKey=]) 62 | create a test plan 63 | 64 | More information about this library can be found on the Wiki_ 65 | 66 | .. _TestLink: http://testlink.org 67 | .. _JinFeng: http://www.sqaopen.net/blog/en/?p=63 68 | .. _Robot Framework: http://code.google.com/p/robotframework 69 | .. _Jenkins: http://jenkins-ci.org 70 | .. _Wiki: https://github.com/lczub/TestLink-API-Python-client/wiki 71 | 72 | """[1:-1] 73 | 74 | setup(name='TestLink-API-Python-client', 75 | version=VERSION, 76 | description='Python XML-RPC client for TestLink %s' % TL_RELEASE, 77 | long_description = DESCRIPTION, 78 | author='James Stock, Olivier Renault, Luiko Czub, TestLink-API-Python-client developers', 79 | author_email='orenault@gmail.com, Luiko.Czub@Liegkat-Archiv.de', 80 | url='https://github.com/lczub/TestLink-API-Python-client', 81 | license = 'Apache 2.0', 82 | package_dir = {'': 'src'}, 83 | packages = ['testlink'], 84 | classifiers = CLASSIFIERS, 85 | platforms = 'any', 86 | keywords = ['testing', 'testlink', 'xml-rpc', 'testautomation'] 87 | 88 | ) 89 | -------------------------------------------------------------------------------- /robot/tlweb.robot: -------------------------------------------------------------------------------- 1 | # Copyright 2014-2019 Luiko Czub 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | *** Settings *** 16 | Documentation Smoke tests for Library TestlinkSeLibExtension 17 | ... 18 | ... Selenium2Library extension to collect and verify test data in 19 | ... TestLink web applications for Testlink XMLRPC api tests. 20 | ... 21 | ... Login - Open Test Specification and Test Project Management pages - Logout 22 | ... 23 | ... pybot parameter for TestLink Demo applikation 24 | ... 25 | ... --variable SERVER:demo.testlink.org/latest --variable DELAY:0.2 26 | Suite Setup Open Browser with TestLink Page 27 | Suite Teardown Close Browser 28 | Test Setup Unselect Frame 29 | Force Tags web 30 | Default Tags general 31 | Resource tlweb_resource.robot 32 | 33 | *** Test Cases *** 34 | Login TestLink 35 | [Documentation] Log into the TestLink web application can be done with 36 | ... _TestlinkSeLibExtension_ keyword *Login TestLink*. 37 | Login TestLink 38 | Location Should Contain caller=login 39 | 40 | Click Titlebar Element - Test Specification 41 | [Documentation] Open Test Specification page can be done with 42 | ... _TestlinkSeLibExtension_ keyword *Click Titlebar Element*. 43 | Click Titlebar Element Test Specification mainframe 44 | Wait Until Frame Contains Element mainframe treeframe 45 | Capture Page Screenshot 46 | 47 | Click Desktop Link - Test Project Management 48 | [Documentation] Open Test Project Management page can be done with 49 | ... _TestlinkSeLibExtension_ keyword *Click Titlebar Element*. 50 | Click Desktop Link Test Project Management search 51 | Capture Page Screenshot 52 | 53 | Get List with Identifier of All Visible Test Projects 54 | [Documentation] List with Identifier of all visible Test Projects can be 55 | ... created with _TestlinkSeLibExtension_ keyword 56 | ... *Get All Visible Projects Identifier*. 57 | @{tp_infos}= Get All Visible Projects Identifier 58 | Length Should Be ${tp_infos} 4 59 | ${tp_count}= Get Length @{tp_infos}[0] 60 | Length Should Be @{tp_infos}[1] ${tp_count} 61 | Length Should Be @{tp_infos}[2] ${tp_count} 62 | Length Should Be @{tp_infos}[3] ${tp_count} 63 | 64 | Select TestProject 65 | [Documentation] selects the second test project in titlebars project list 66 | Select Frame titlebar 67 | Log Source 68 | Capture Page Screenshot 69 | @{tprojects}= Get List Items testproject 70 | Select From List By Index testproject 2 71 | Capture Page Screenshot 72 | Log Source 73 | ${tp_value}= Execute Javascript return document.getElementsByName("testproject")[0].options[0].value 74 | ${tp_title}= Execute Javascript return document.getElementsByName("testproject")[0].options[0].title 75 | 76 | Click Desktop Link - Test Specification 77 | [Documentation] Open Test Specification page can be done with 78 | ... _TestlinkSeLibExtension_ keyword *Click Desktop Link*. 79 | Click Desktop Link Test Specification treeframe 80 | Capture Page Screenshot 81 | 82 | Logout TestLink 83 | [Documentation] Log out of the TestLink web application can be done with 84 | ... _TestlinkSeLibExtension_ keyword *Logout TestLink*. 85 | Logout TestLink 86 | Location Should Contain login.php 87 | -------------------------------------------------------------------------------- /example/all-req.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | a section

]]> 13 |
14 | 15 | 16 | <![CDATA[use case 01]]> 17 | 1 18 | 1 19 | 1 20 | a use case

]]>
21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | <![CDATA[none function requirement 01]]> 29 | 1 30 | 1 31 | 2 32 | a none functional requirement

]]>
33 | 34 | 35 | 36 | 37 |
38 | 39 | none-function-01 40 | use-case-01 41 | 2 42 | 43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | a user requirement

]]> 53 |
54 | 55 | 56 | <![CDATA[restriction 01]]> 57 | 1 58 | 1 59 | 1 60 | a restriction

]]>
61 | 62 | 63 | 64 | 65 |
66 | 67 | 68 | <![CDATA[user gui 01]]> 69 | 1 70 | 1 71 | 2 72 | a user gui

]]>
73 | 74 | 75 | 76 | 77 |
78 | 79 | system-func-01 80 | user-gui-01 81 | 2 82 | 83 |
84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | a system requirement

]]> 93 |
94 | 95 | 96 | <![CDATA[system function 01]]> 97 | 1 98 | 1 99 | 2 100 | a system function

]]>
101 | 102 | 103 | 104 | 105 |
106 | 107 | 108 | <![CDATA[feature 01]]> 109 | 1 110 | 1 111 | 3 112 | a feature

]]>
113 | 114 | 115 | 116 | 117 |
118 | 119 | system-func-01 120 | user-gui-01 121 | 2 122 | 123 |
124 | -------------------------------------------------------------------------------- /doc/install.rst: -------------------------------------------------------------------------------- 1 | TestLink-API-Python-client Installation 2 | ======================================= 3 | 4 | .. contents:: 5 | :local: 6 | 7 | Preconditions 8 | ------------- 9 | 10 | Currently the combinations Python 2.7.16 / 3.6.8 / 3.7.4 are tested with 11 | TestLink 1.9.20 (development state, github a1c7aca97). Other combination might work - feedback is welcome :-) 12 | 13 | TestLink configuration 14 | ---------------------- 15 | 16 | The TestLink configuration (config.inc.php or custom_config.inc.php) must have 17 | enabled the api interface:: 18 | 19 | $tlCfg->api->enabled = TRUE; 20 | 21 | Create the user specific devKey inside TestLink, see 22 | 23 | - My Settings -> API interface - Personal API access key [Generate a new key] 24 | 25 | Installing from PyPI 26 | -------------------- 27 | 28 | TestLink-API-Python-client is available in the Python Package Index (PyPI_) 29 | since Release v0.4.6. It is recommended that you use `pip`_ to install. 30 | 31 | Install from PyPI using pip by running:: 32 | 33 | pip install TestLink-API-Python-client 34 | 35 | Installing from source distribution 36 | ----------------------------------- 37 | 38 | The source code can be retrieved as source distribution either 39 | 40 | - as stable release from SourceForge_ 41 | - or as development version from `GitHup Releases`_ 42 | 43 | Install the archives using pip by running:: 44 | 45 | pip install TestLink-API-Python-client-0.8.1.zip 46 | 47 | Installing from source 48 | ---------------------- 49 | 50 | Install the extracted source distribution or download of the last (none tested) 51 | changes from `GitHup Master`_ by running:: 52 | 53 | python setup.py install 54 | 55 | Verifying Installation 56 | ---------------------- 57 | 58 | Check, if Python could talk with TestLink using the connection parameter 59 | 60 | - SERVER_URL = http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php 61 | - DEVKEY = [Users devKey generated by TestLink] 62 | 63 | as init parameter:: 64 | 65 | python 66 | >>> import testlink 67 | >>> tls = testlink.TestlinkAPIClient(SERVER_URL, DEVKEY) 68 | >>> tls.about() 69 | ' Testlink API Version: 1.0 initially ....' 70 | 71 | or by defining the connection parameter as environment variables:: 72 | 73 | set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php 74 | set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink] 75 | python 76 | >>> import testlink 77 | >>> tls = testlink.TestLinkHelper(testlink.TestlinkAPIClient) 78 | >>> tls.about() 79 | ' Testlink API Version: 1.0 initially ....' 80 | 81 | Changed SERVER_URL with TestLink 1.9.7 82 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 83 | 84 | The SERVER_URL path has changed with TestLink 1.9.7. 85 | 86 | - TL Version < 1.9.7: Use http://[YOURSERVER]/testlink/lib/api/xmlrpc.php 87 | - TL Version >=1.9.7: Use http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php 88 | 89 | Install in virtualenv TestLink 90 | ------------------------------ 91 | 92 | Cause most linux system (and although newer windows systems too) expect root 93 | privileges for installation, a separate TestLink virtualenv_ may help:: 94 | 95 | [PYTHON27]\Scripts\virtualenv [PYENV]\testlink 96 | [PYENV]\testlink\Scripts\activate 97 | pip install TestLink-API-Python-client 98 | 99 | If you always work with the same TestLink server, extend the script 100 | 101 | - [PYENV]/testlink/Scripts/activate[.bat|.ps1] 102 | 103 | with connection parameter as environment variables 104 | 105 | - TESTLINK_API_PYTHON_SERVER_URL and TESTLINK_API_PYTHON_DEVKEY 106 | 107 | 108 | .. _PyPI: https://pypi.python.org/pypi 109 | .. _pip: http://www.pip-installer.org 110 | .. _SourceForge: http://sourceforge.net/projects/testlink-api-python-client/files/latest/download 111 | .. _GitHup Releases: https://github.com/lczub/TestLink-API-Python-client/releases 112 | .. _GitHup Master: https://github.com/lczub/TestLink-API-Python-client/archive/master.zip 113 | .. _virtualenv: http://www.virtualenv.org/en/latest/virtualenv.html 114 | .. _issue 50: https://github.com/lczub/TestLink-API-Python-client/issues/50 115 | -------------------------------------------------------------------------------- /src/testlink/testlinkargs.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | from .testlinkerrors import TLArgError 21 | 22 | 23 | __doc__ = """ This internal module is used as a 'singleton' to register the 24 | supported TestLink API methods and there (positional and optional) arguments """ 25 | 26 | 27 | # main hash, where the registered methods and there arguments are stored 28 | # PLEASE never manipulate the entries directly. 29 | # 30 | # definitions structure is 31 | # key(apiMethodeName) = ( [default positional apiArgs], [all apiArgs], 32 | # [other mandatory non api Args] ) 33 | # [all apiArgs] includes all positional and optional args without other 34 | # mandatory Args 35 | _apiMethodsArgs = {} 36 | 37 | 38 | def _resetRegister(): 39 | """ clears all entries in _apiMethodsArgs""" 40 | _apiMethodsArgs.clear() 41 | 42 | 43 | def _getMethodsArgDefinition(methodName): 44 | """ returns argument definition for api methodName """ 45 | 46 | try: 47 | return _apiMethodsArgs[methodName] 48 | except KeyError: 49 | raise TLArgError('apiMethod %s not registered!' % methodName) 50 | 51 | 52 | def registerMethod(methodName, apiArgsPositional=[], apiArgsOptional=[], 53 | otherArgsMandatory=[]): 54 | """ extend _apiMethodsArgs with a new definition structure for METHODNAME 55 | 56 | definitions structure is 57 | key(apiMethodeName) = ( [default positional apiArgs], [all apiArgs], 58 | [other mandatory non api Args] ) 59 | [all apiArgs] includes all positional and optional args without other 60 | mandatory Args """ 61 | 62 | if methodName in _apiMethodsArgs: 63 | raise TLArgError('apiMethod %s already registered!' % methodName) 64 | 65 | allArgs = apiArgsPositional[:] 66 | for argName in apiArgsOptional: 67 | if not argName in allArgs: 68 | allArgs.append(argName) 69 | _apiMethodsArgs[methodName] = (apiArgsPositional[:], allArgs, 70 | otherArgsMandatory[:]) 71 | 72 | 73 | def registerArgOptional(methodName, argName): 74 | """ Update _apiMethodsArgs[methodName] with additional optional argument """ 75 | 76 | allArgs = _getMethodsArgDefinition(methodName)[1] 77 | if not argName in allArgs: 78 | allArgs.append(argName) 79 | 80 | 81 | def registerArgNonApi(methodName, argName): 82 | """ Update _apiMethodsArgs[methodName] with additional non api argument """ 83 | 84 | nonApiArgs = _getMethodsArgDefinition(methodName)[2] 85 | if not argName in nonApiArgs: 86 | nonApiArgs.append(argName) 87 | 88 | 89 | def getMethodsWithPositionalArgs(): 90 | """ returns a dictionary with method names and there positional args """ 91 | positionalArgNames = {} 92 | for mname, argdef in list(_apiMethodsArgs.items()): 93 | # does method MNAME has defined positional arguments? 94 | if argdef[0]: 95 | positionalArgNames[mname] = argdef[0][:] 96 | return positionalArgNames 97 | 98 | # def getApiArgsForMethod(methodName): 99 | # """ returns list with all api argument name for METHODNAME """ 100 | # return _getMethodsArgDefinition(methodName)[1][:] 101 | 102 | 103 | def getArgsForMethod(methodName, knownArgNames=[]): 104 | """ returns for METHODNAME additional arg names as a tuple with two lists 105 | a) optional api arguments, not listed in knownArgNames 106 | b) additional mandatory non api arguments 107 | 108 | raise TLArgError, if METHODNAME is not registered """ 109 | 110 | # argument definitions in _apiMethodsArgs 111 | argDef = _getMethodsArgDefinition(methodName) 112 | 113 | # find missing optional arg names 114 | apiArgsAll = argDef[1] 115 | apiArgs = [x for x in apiArgsAll if x not in knownArgNames] 116 | 117 | # other mandatory arg names 118 | manArgs = argDef[2][:] 119 | 120 | return (apiArgs, manArgs) 121 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | TestLink API Python Client 2 | ========================== 3 | 4 | Copyright 2011-2022 5 | James Stock, Olivier Renault, Luiko Czub, TestLink-API-Python-client developers 6 | 7 | License `Apache License 2.0`_ 8 | 9 | Introduction 10 | ------------ 11 | 12 | TestLink-API-Python-client is a Python XML-RPC client for TestLink_. 13 | 14 | Initially based on James Stock testlink-api-python-client R7 and Olivier 15 | Renault JinFeng_ idea - an interaction of TestLink_, `Robot Framework`_ and Jenkins_. 16 | 17 | TestLink-API-Python-client delivers two main classes 18 | 19 | - TestlinkAPIGeneric - Implements the TestLink API methods as generic PY methods 20 | with error handling 21 | - TestlinkAPIClient - Inherits from TestlinkAPIGeneric and defines service 22 | methods like "copyTCnewVersion". 23 | 24 | and the helper class 25 | 26 | - TestLinkHelper - search connection parameter from environment variables and 27 | command line arguments 28 | 29 | How to talk with TestLink in a python shell and copy a test case: :: 30 | 31 | set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php 32 | set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink] 33 | python 34 | 35 | >>> import testlink 36 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 37 | >>> tls.countProjects() 38 | 3 39 | >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3') 40 | [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2', 41 | 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}] 42 | >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], testsuiteid=newSuiteID, 43 | testcasename='a new test case name') 44 | >>> print tls.whatArgs('createTestPlan') 45 | createTestPlan(, , [note=], [active=], 46 | [public=], [devKey=]) 47 | create a test plan 48 | 49 | Installation 50 | ------------ 51 | 52 | Install the latest stable release from PyPI using pip by running 53 | 54 | pip install TestLink-API-Python-client 55 | 56 | Directory Layout 57 | ---------------- 58 | 59 | src/ 60 | Source for TestLink API Python Client 61 | 62 | doc/ 63 | `Installation`_ and `Usage (EN)`_ / `Usage (FR)`_ documentation 64 | 65 | examples/ 66 | Examples, how to use `TestlinkAPIGeneric`_ and `TestlinkAPIClient`_. 67 | For (nearly all) implemented API methods you find an example, how to 68 | call it and how the response looks like. 69 | 70 | tests/ 71 | Unit Tests for TestLink API Python Client 72 | 73 | `tox.ini`_ 74 | Configuration for multi Python version testing via `Tox`_ 75 | 76 | 77 | Help 78 | ---- 79 | 80 | Questions, Enhancements, Issues are welcome under `Issues`_ 81 | 82 | Take a look into ``_ for additional details, what have changed and 83 | how existing code can be adapted 84 | 85 | 86 | TestLink-API-Python-client developers 87 | ------------------------------------- 88 | * `James Stock`_, `Olivier Renault`_, `lczub`_, `manojklm`_ (PY3) 89 | * `g4l4drim`_, `pade`_, `anton-matosov`_, `citizen-stig`_, `charz`_, `Maberi`_ 90 | * `Brian-Williams`_, `alexei-drozdov`_, `janLo`_, `heuj`_, `elapfra`_ 91 | * `Mikycid`_, anyone forgotten? 92 | 93 | .. _Apache License 2.0: http://www.apache.org/licenses/LICENSE-2.0 94 | .. _TestLink: http://testlink.org 95 | .. _JinFeng: http://www.sqaopen.net/blog/en/?p=63 96 | .. _Robot Framework: http://code.google.com/p/robotframework 97 | .. _Jenkins: http://jenkins-ci.org 98 | .. _Installation: doc/install.rst 99 | .. _Usage (EN): doc/usage.rst 100 | .. _Usage (FR): doc/fr_usage.rst 101 | .. _TestlinkAPIGeneric: example/TestLinkExampleGenericApi.py 102 | .. _TestlinkAPIClient: example/TestLinkExample.py 103 | .. _tox.ini: tox.ini 104 | .. _Tox: http://tox.readthedocs.org/en/latest/ 105 | .. _Issues: https://github.com/lczub/TestLink-API-Python-client/issues 106 | .. _Olivier Renault: https://github.com/orenault/TestLink-API-Python-client 107 | .. _pade: https://github.com/pade/TestLink-API-Python-client 108 | .. _g4l4drim: https://github.com/g4l4drim/TestLink-API-Python-client 109 | .. _James Stock: https://code.google.com/p/testlink-api-python-client/ 110 | .. _lczub: https://github.com/lczub/TestLink-API-Python-client 111 | .. _anton-matosov: https://github.com/anton-matosov/TestLink-API-Python-client 112 | .. _citizen-stig: https://github.com/citizen-stig/TestLink-API-Python-client 113 | .. _charz: https://github.com/charz/TestLink-API-Python-client.git 114 | .. _manojklm: https://github.com/manojklm/TestLink-API-Python-client 115 | .. _Maberi: https://github.com/Maberi/TestLink-API-Python-client 116 | .. _Brian-Williams: https://github.com/Brian-Williams/TestLink-API-Python-client 117 | .. _alexei-drozdov: https://github.com/alexei-drozdov/TestLink-API-Python-client 118 | .. _janLo: https://github.com/janLo/TestLink-API-Python-client 119 | .. _heuj: https://github.com/heuj/TestLink-API-Python-client 120 | .. _elapfra: https://github.com/elapfra/TestLink-API-Python-client 121 | .. _Mikycid: https://github.com/Mikycid/TestLink-API-Python-client -------------------------------------------------------------------------------- /test/utest-online/test_apiCall_differentPositionalArgs.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2012-2019 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | # TestCases for Testlink API calls, where the Api Clients uses different 21 | # positional arg configurations 22 | # - TestlinkAPIClient, TestlinkAPIGeneric 23 | # 24 | # 25 | # this test requires an online TestLink Server, which connection parameters 26 | # are defined in environment variables 27 | # TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY 28 | # 29 | # FIME LC 29.10.29: test does not really interacts with test link 30 | # only negative test with none existing IDs implemented 31 | # ok to check every implemented server call one time but not 32 | # to cover all possible responses or argument combinations 33 | 34 | import pytest 35 | from testlink.testlinkerrors import TLResponseError 36 | 37 | class Test_TestlinkAPIClient_Behaviour(): 38 | ''' Test api call with positional arg configuration TestlinkAPIClient ''' 39 | 40 | def test_getLastExecutionResult_unknownID(self, api_general_client): 41 | with pytest.raises(TLResponseError, match='3000.*40000711'): 42 | api_general_client.getLastExecutionResult(40000711, 40000712) 43 | 44 | def test_getTestCaseCustomFieldDesignValue_unknownID(self, api_general_client): 45 | with pytest.raises(TLResponseError, match='7000.*40000711'): 46 | api_general_client.getTestCaseCustomFieldDesignValue( 47 | 'TC-40000712', 1, 40000711, 'a_field', 'a_detail') 48 | 49 | def test_getTestCasesForTestSuite_unknownID(self, api_general_client): 50 | with pytest.raises(TLResponseError, match='8000.*40000711'): 51 | api_general_client.getTestCasesForTestSuite(40000711, 2, 'a_detail') 52 | 53 | def test_createBuild_unknownID(self, api_general_client): 54 | with pytest.raises(TLResponseError, match='3000.*40000711'): 55 | api_general_client.createBuild(40000711, 'Build 40000712', 'note 40000713') 56 | 57 | def test_createTestCase_unknownID(self, api_general_client): 58 | with pytest.raises(TLResponseError, match='7000.*40000713'): 59 | api_general_client.createTestCase('case 40000711', 40000712, 40000713, 60 | 'Big Bird', 'summary 40000714') 61 | 62 | def test_reportTCResult_unknownID(self, api_general_client): 63 | with pytest.raises(TLResponseError, match='5000.*40000711'): 64 | api_general_client.reportTCResult(40000711, 40000712, 'build 40000713', 'p', 65 | 'note 40000714') 66 | 67 | def test_uploadExecutionAttachment_unknownID(self, api_general_client, attachmentFile): 68 | with pytest.raises(TLResponseError, match='6004.*40000712'): 69 | api_general_client.uploadExecutionAttachment(attachmentFile, 40000712, 70 | 'title 40000713', 'descr. 40000714') 71 | 72 | class Test_TestlinkAPIGeneric_Behaviour(): 73 | ''' Test api call with positional arg configuration TestlinkAPIGeneric ''' 74 | 75 | def test_getLastExecutionResult_unknownID(self, api_generic_client): 76 | with pytest.raises(TLResponseError, match='3000.*40000711'): 77 | api_generic_client.getLastExecutionResult(40000711, testcaseid=40000712) 78 | 79 | def test_getTestCaseCustomFieldDesignValue_unknownID(self, api_generic_client): 80 | with pytest.raises(TLResponseError, match='7000.*40000711'): 81 | api_generic_client.getTestCaseCustomFieldDesignValue( 82 | 'TC-40000712', 1, 40000711, 'a_field', details='full') 83 | 84 | def test_getTestCasesForTestSuite_unknownID(self, api_generic_client): 85 | with pytest.raises(TLResponseError, match='8000.*40000711'): 86 | api_generic_client.getTestCasesForTestSuite(40000711) 87 | 88 | def test_createBuild_unknownID(self, api_generic_client): 89 | with pytest.raises(TLResponseError, match='3000.*40000711'): 90 | api_generic_client.createBuild(40000711, 'Build 40000712', buildnotes='note 40000713') 91 | 92 | def test_createTestCase_unknownID(self, api_generic_client): 93 | tc_steps = [] 94 | with pytest.raises(TLResponseError, match='7000.*40000713'): 95 | api_generic_client.createTestCase('case 40000711', 40000712, 40000713, 96 | 'Big Bird', 'summary 40000714', tc_steps) 97 | 98 | def test_reportTCResult_unknownID(self, api_generic_client): 99 | with pytest.raises(TLResponseError, match='5000.*40000711'): 100 | api_generic_client.reportTCResult(40000712, 'p', testcaseid=40000711, 101 | buildname='build 40000713', notes='note 40000714' ) 102 | 103 | def test_uploadExecutionAttachment_unknownID(self, api_generic_client, attachmentFile): 104 | with pytest.raises(TLResponseError, match='6004.*40000712'): 105 | api_generic_client.uploadExecutionAttachment(attachmentFile, 40000712, 106 | title='title 40000713', description='descr. 40000714') 107 | 108 | -------------------------------------------------------------------------------- /test/utest-offline/testlinkargs_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | # this test works WITHOUT an online TestLink Server 21 | # no calls are send to a TestLink Server 22 | 23 | import pytest 24 | # from testlink.testlinkapigeneric import testlinkargs 25 | from testlink import testlinkargs 26 | 27 | @pytest.fixture() 28 | def args_register(): 29 | """ reset singelton testlinkargs before and after the test """ 30 | testlinkargs._resetRegister() 31 | yield testlinkargs 32 | testlinkargs._resetRegister() 33 | 34 | def test__resetRegister(args_register): 35 | args_register._apiMethodsArgs['BigBird'] = 'not a Small Bird' 36 | assert None != args_register._apiMethodsArgs.get('BigBird') 37 | args_register._resetRegister() 38 | assert None == args_register._apiMethodsArgs.get('BigBird') 39 | 40 | 41 | def test_registerMethod(args_register): 42 | args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], 43 | ['quad','tre'], ['cinque']) 44 | a_def = args_register._apiMethodsArgs['DummyMethod'] 45 | assert a_def == (['Uno', 'due', 'tre'], ['Uno', 'due', 'tre', 'quad'], 46 | ['cinque']) 47 | 48 | def test_registerMethod_noArgs(args_register): 49 | args_register.registerMethod('DummyMethod') 50 | a_def = args_register._apiMethodsArgs['DummyMethod'] 51 | assert a_def == ([], [], []) 52 | 53 | def test_registerMethod_onlyArgsOptional(args_register): 54 | args_register.registerMethod('DummyMethod', apiArgsOptional=['quad','tre']) 55 | a_def = args_register._apiMethodsArgs['DummyMethod'] 56 | assert a_def == ([], ['quad','tre'], []) 57 | 58 | def test_registerMethod_onlyArgsPositional(args_register): 59 | args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre']) 60 | a_def = args_register._apiMethodsArgs['DummyMethod'] 61 | assert a_def == (['Uno', 'due', 'tre'], ['Uno', 'due', 'tre'], []) 62 | 63 | def test_getMethodsWithPositionalArgs(args_register): 64 | args_register.registerMethod('Method_3pos_0opt', ['Uno', 'due', 'tre']) 65 | args_register.registerMethod('Method_0pos_2opt', [], ['Uno', 'due']) 66 | args_register.registerMethod('Method_1pos_2opt', ['Uno'], ['due', 'tre']) 67 | a_def = args_register.getMethodsWithPositionalArgs() 68 | assert a_def == {'Method_3pos_0opt' : ['Uno', 'due', 'tre'], 69 | 'Method_1pos_2opt' : ['Uno']} 70 | 71 | def test_registerMethod_ErrorAlreadyDefined(args_register): 72 | args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], 73 | ['quad','tre'], ['cinque']) 74 | with pytest.raises(testlinkargs.TLArgError, 75 | match='DummyMethod already registered'): 76 | args_register.registerMethod('DummyMethod') 77 | 78 | def test_registerArgOptional(args_register): 79 | args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], 80 | ['quad','tre'], ['cinque']) 81 | args_register.registerArgOptional('DummyMethod', 'sei') 82 | a_def = args_register._apiMethodsArgs['DummyMethod'] 83 | assert a_def == (['Uno', 'due', 'tre'], 84 | ['Uno', 'due', 'tre', 'quad', 'sei'], ['cinque']) 85 | 86 | def test_registerArgOptional_ErrorUnknownMethod(args_register): 87 | with pytest.raises(testlinkargs.TLArgError, 88 | match='DummyMethod not registered'): 89 | args_register.registerArgOptional('DummyMethod', 'sei') 90 | 91 | def test_registerArgNonApi(args_register): 92 | args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], 93 | ['quad','tre'], ['cinque']) 94 | args_register.registerArgNonApi('DummyMethod', 'sei') 95 | a_def = args_register._apiMethodsArgs['DummyMethod'] 96 | assert a_def == (['Uno', 'due', 'tre'], ['Uno', 'due', 'tre', 'quad'], 97 | ['cinque', 'sei']) 98 | 99 | def test_getArgsForMethod_onlyOptionalArgs(args_register): 100 | args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], 101 | ['quad','tre']) 102 | response = args_register.getArgsForMethod('DummyMethod') 103 | assert response == (['Uno', 'due', 'tre', 'quad'], []) 104 | 105 | def test_getArgsForMethod_OptionalAndPositionalArgs(args_register): 106 | args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], 107 | ['quad','tre']) 108 | response = args_register.getArgsForMethod('DummyMethod', ['Uno', 'quad']) 109 | assert response == (['due', 'tre'], []) 110 | 111 | def test_getArgsForMethod_nonApiArgs(args_register): 112 | args_register.registerMethod('DummyMethod', ['Uno', 'due', 'tre'], 113 | ['quad','tre'], ['cinque']) 114 | response = args_register.getArgsForMethod('DummyMethod', 115 | ['Uno', 'due', 'tre']) 116 | assert response == (['quad'], ['cinque']) 117 | 118 | def test_getArgsForMethod_unknownMethods(args_register): 119 | with pytest.raises(testlinkargs.TLArgError, 120 | match='unknownMethod not registered'): 121 | args_register.getArgsForMethod('unknownMethod') 122 | -------------------------------------------------------------------------------- /src/testlink/testlinkhelper.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2012-2020 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | import os, sys 21 | from argparse import ArgumentParser 22 | from .version import VERSION 23 | import ssl 24 | 25 | IS_PY3 = sys.version_info[0] > 2 26 | if IS_PY3: 27 | from .proxiedtransport3 import ProxiedTransport 28 | else: 29 | from .proxiedtransport2 import ProxiedTransport 30 | 31 | class TestLinkHelper(object): 32 | """ Helper Class to find out the TestLink connection parameters. 33 | a) TestLink Server URL of XML-RPC 34 | environment variable - TESTLINK_API_PYTHON_SERVER_URL 35 | default value - http://localhost/testlink/lib/api/xmlrpc.php 36 | command line arg - server_url 37 | b) Users devKey generated by TestLink 38 | environment variable - TESTLINK_API_PYTHON_DEVKEY 39 | default value - 42 40 | command line arg - devKey 41 | c) Users devKey generated by TestLink 42 | environment variable - http_proxy 43 | default value - not set ('') 44 | command line arg - proxy 45 | 46 | Examples 1 - init TestlinkAPIClient with environment variables 47 | - define connection parameters in environment variables 48 | TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY 49 | - TestLinkHelper().connect(TestlinkAPIClient) 50 | -> returns a TestlinkAPIClient instance 51 | 52 | Examples 2 - init TestLink with command line arguments 53 | - call python module with command line arguments --server_url and --devKey 54 | TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY 55 | - tl_helper = TestLinkHelper() 56 | tl_helper.setParamsFromArgs() 57 | tl_helper.connect(TestLink) 58 | -> returns a TestLink instance 59 | 60 | Attention: TL 197 changed the URL of XML-RPC 61 | from http://localhost/testlink/lib/api/xmlrpc.php 62 | to http://localhost/testlink/lib/api/xmlrpc/v1/xmlrpc.php 63 | """ 64 | 65 | __slots__ = ['_server_url', '_devkey', '_proxy'] 66 | 67 | ENVNAME_SERVER_URL = 'TESTLINK_API_PYTHON_SERVER_URL' 68 | ENVNAME_DEVKEY = 'TESTLINK_API_PYTHON_DEVKEY' 69 | ENVNAME_PROXY = 'http_proxy' 70 | DEFAULT_SERVER_URL = 'http://localhost/testlink/lib/api/xmlrpc.php' 71 | DEFAULT_DEVKEY = '42' 72 | DEFAULT_PROXY = '' 73 | DEFAULT_DESCRIPTION = 'Python XML-RPC client for the TestLink API v%s' \ 74 | % VERSION 75 | 76 | def __init__(self, server_url=None, devkey=None, proxy=None): 77 | """ fill slots _server_url, _devkey and _proxy 78 | Priority: 79 | 1. init args 80 | 2. environment variables 81 | 3. default values 82 | """ 83 | self._server_url = server_url 84 | self._devkey = devkey 85 | self._proxy = proxy 86 | self._setParams() 87 | 88 | def _setParams(self): 89 | self._setParamsFromEnv() 90 | 91 | def _setParamsFromEnv(self): 92 | """ fill empty slots from environment variables 93 | _server_url <- TESTLINK_API_PYTHON_SERVER_URL 94 | _devkey <- TESTLINK_API_PYTHON_DEVKEY 95 | _proxy <- http_proxy 96 | 97 | If environment variables are not defined, defaults values are set. 98 | """ 99 | if self._server_url is None: 100 | self._server_url = os.getenv(self.ENVNAME_SERVER_URL, 101 | self.DEFAULT_SERVER_URL) 102 | if self._devkey is None: 103 | self._devkey = os.getenv(self.ENVNAME_DEVKEY, self.DEFAULT_DEVKEY) 104 | 105 | if self._proxy is None: 106 | self._proxy = os.getenv(self.ENVNAME_PROXY, self.DEFAULT_PROXY) 107 | 108 | def _createArgparser(self, usage): 109 | """ returns a parser for command line arguments """ 110 | 111 | a_parser = ArgumentParser( description=usage) 112 | # optional command line parameters 113 | a_parser.add_argument('--server_url', default=self._server_url, 114 | help='TestLink Server URL of XML-RPC (default: %(default)s) ') 115 | a_parser.add_argument('--proxy', default=self._proxy, 116 | help='HTTP Proxy (default: %(default)s) ') 117 | # pseudo optional command line parameters, 118 | # must be set individual for each user 119 | a_parser.add_argument('--devKey', default=self._devkey, 120 | help='Users devKey generated by TestLink (default: %(default)s) ') 121 | return a_parser 122 | 123 | def setParamsFromArgs(self, usage=DEFAULT_DESCRIPTION, args=None): 124 | """ fill slots from command line arguments 125 | _server_url <- --server_url 126 | _devkey <- --devKey 127 | _proxy <- --proxy 128 | 129 | uses current values of these slots as default values 130 | """ 131 | a_parser = self._createArgparser(usage) 132 | args = a_parser.parse_args(args) 133 | self._server_url = args.server_url 134 | self._devkey = args.devKey 135 | self._proxy = args.proxy 136 | 137 | def _getProxiedTransport(self): 138 | """ creates and return a ProxiedTransport with self._proxy settings """ 139 | 140 | a_pt = ProxiedTransport() 141 | a_pt.set_proxy(self._proxy) 142 | return a_pt 143 | 144 | def connect(self, tl_api_class, **kwargs): 145 | """ returns a new instance of TL_API_CLASS """ 146 | 147 | if self._proxy: 148 | kwargs['transport'] = self._getProxiedTransport() 149 | 150 | # must we add an uncertified context for a https connection? 151 | # only, if kwargs not includes a context 152 | if self._server_url.lower().startswith('https'): 153 | if not ('context' in kwargs): 154 | kwargs['context'] = ssl._create_unverified_context() 155 | 156 | return tl_api_class(self._server_url, self._devkey, **kwargs) 157 | -------------------------------------------------------------------------------- /robot/TestlinkSeLibExtension.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2014-2019 Luiko Czub 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | from robot.libraries.BuiltIn import BuiltIn 21 | 22 | class TestlinkSeLibExtension(object): 23 | """ Extension of Robot Framework Selenium2library to access a TestLink web 24 | application to collect and verify test data for Testlink XMLRPC api tests. 25 | """ 26 | 27 | ROBOT_LIBRARY_SCOPE = 'TEST_SUITE' 28 | 29 | def __init__(self, url='http://demo.testlink.org/latest', 30 | username='admin', password='admin' ): 31 | self._get_selenium_lib_instance() 32 | self._store_server_url(url) 33 | self._store_user(username, password) 34 | 35 | def _store_server_url(self, url): 36 | self._server_url = url 37 | 38 | def _store_user(self, username, password): 39 | self._username = username 40 | self._password = password 41 | 42 | def _get_selenium_lib_instance(self): 43 | """ Gets current running Selenium2Library instance and store it 44 | Uses BuiltIn().get_library_instance(), which normally should work also 45 | during __init__ 46 | - see https://code.google.com/p/robotframework/issues/detail?id=646 47 | But RIDE makes problems - the RIDE logs shows a traceback 48 | - AttributeError: 'NoneType' object has no attribute 'namespace' 49 | after selecting a testsuite only for reading. 50 | To avoid this traceback, we will catch it and do nothing. 51 | """ 52 | try: 53 | self._seleniumlib = BuiltIn().get_library_instance('Selenium2Library') 54 | except AttributeError: 55 | # we do nothing see method comment 56 | pass 57 | 58 | def login_TestLink(self): 59 | """ Logs into the TestLink web app """ 60 | SelLib = self._seleniumlib 61 | SelLib.location_should_contain('login.php') 62 | SelLib.input_text('tl_login', self._username) 63 | SelLib.input_text('tl_password', self._password) 64 | SelLib.click_button('login_submit') 65 | 66 | def logout_TestLink(self): 67 | """ Logs out of the TestLink web app""" 68 | SelLib = self._seleniumlib 69 | self.click_titlebar_element('Logout', 'login_div') 70 | 71 | def click_frame_element(self, frame_name, frame_elem, frame_unselect=True): 72 | """ Select frame FRAME_NAME and clicks on element FRAME_ELEM. 73 | Unselect this frame at the end, if FRAME_UNSELECT is True (default). 74 | internal keyword for testing the TestLink web app which uses framesets 75 | """ 76 | SelLib = self._seleniumlib 77 | SelLib.select_frame(frame_name) 78 | SelLib.click_element(frame_elem) 79 | if frame_unselect is True: 80 | SelLib.unselect_frame() 81 | 82 | def click_titlebar_element(self, tbar_elem, expected_elem): 83 | """ Clicks in TestLinks *titlebar* frame on image with title TBAR_ELEM. 84 | Waits till _mainframe_ includes the element EXPECTED_ELEM. """ 85 | SelLib = self._seleniumlib 86 | SelLib.log_location() 87 | img_xpath = "xpath=//img[@title='%s']" % tbar_elem 88 | self.click_frame_element('titlebar', img_xpath) 89 | SelLib.wait_until_page_contains_element(expected_elem) 90 | 91 | def click_desktop_link(self, desktop_link, expected_elem): 92 | """ Opens TestLinks *Desktop* page and clicks on link DESKTOP_LINK. 93 | Waits till _mainframe_ includes the element EXPECTED_ELEM. """ 94 | SelLib = self._seleniumlib 95 | self.click_titlebar_element('Desktop', 'mainframe') 96 | a_link='link=%s' % desktop_link 97 | self.click_frame_element('name=mainframe', a_link) 98 | self.wait_until_frame_contains_element('mainframe', expected_elem) 99 | 100 | def wait_until_frame_contains_element(self, frame_name,frame_elem): 101 | """ Waits till frame FRAME_NAME exists, select this frame and waits 102 | again till the element FRAME_ELEM exists in it. 103 | internal keyword for testing the TestLink web app which uses framesets 104 | """ 105 | SelLib = self._seleniumlib 106 | a_frame = 'name=%s' % frame_name 107 | SelLib.wait_until_page_contains_element(a_frame) 108 | SelLib.select_frame(frame_name) 109 | SelLib.wait_until_page_contains_element(frame_elem) 110 | 111 | def get_all_visible_projects_identifier(self): 112 | """ Return lists with four different identifier for test projects, 113 | visible in TestLinks *titlebar* selecting list *testproject* 114 | 115 | 1. List with test projects internal ID 116 | 2. list with test projects prefix 117 | 3. list with test projects name 118 | 4. list with selection list option text 119 | 120 | All four lists have the same length and order equates to selecting list 121 | *testproject* """ 122 | 123 | js_script = """ 124 | //var myform = document.getElementsByName("titlebar")[0].contentWindow.document.getElementsByName("testproject")[0] 125 | var myform = document.getElementsByName("testproject")[0] 126 | var myOptions = [].slice.call( myform.options ); 127 | var tp_ids = myOptions.map(function(a_opt) {return a_opt.value}) 128 | var tp_prefixs = myOptions.map(function(a_opt) { 129 | var a_title = a_opt.title; 130 | var a_split = a_title.indexOf(":"); 131 | return a_title.substring(0,a_split)}) 132 | var tp_names = myOptions.map(function(a_opt) { 133 | var a_title = a_opt.title; 134 | var a_split = a_title.indexOf(":"); 135 | return a_title.substring(a_split+1)}) 136 | var opt_texts = myOptions.map(function(a_opt) {return a_opt.text}) 137 | return [tp_ids, tp_prefixs, tp_names, opt_texts] 138 | """ 139 | SelLib = self._seleniumlib 140 | SelLib.select_frame('titlebar') 141 | return SelLib.execute_javascript(js_script) 142 | 143 | -------------------------------------------------------------------------------- /example/customFields_ExampleDefs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /test/utest-offline/test_apiClients_whatArgs.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2018-2021 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | # TestCases for Testlink API clients whatArgs calls 21 | # - TestlinkAPIClient, TestlinkAPIGeneric 22 | # 23 | 24 | import pytest 25 | import re 26 | 27 | def test_whatArgs_noArgs(api_client): 28 | response = api_client.whatArgs('sayHello') 29 | assert re.match('sayHello().*', response) 30 | 31 | def test_whatArgs_onlyOptionalArgs(api_client): 32 | response = api_client.whatArgs('getTestCaseKeywords') 33 | assert re.match(r'getTestCaseKeywords\(\[.*=<.*>\].*\).*', 34 | response) 35 | 36 | def test_whatArgs_OptionalAndPositionalArgs(api_client): 37 | response = api_client.whatArgs('createBuild') 38 | assert re.match(r'createBuild\(<.*>.*\).*', response) 39 | 40 | def test_whatArgs_MandatoryArgs(api_client): 41 | response = api_client.whatArgs('uploadExecutionAttachment') 42 | assert re.match(r'uploadExecutionAttachment\(, <.*>.*\).*', 43 | response) 44 | 45 | def test_whatArgs_unknownMethods(api_client): 46 | response = api_client.whatArgs('apiUnknown') 47 | assert re.match(r"callServerWithPosArgs\('apiUnknown', \[apiArg=\]\)", 48 | response) 49 | 50 | test_data_apiCall_descriptions_equal_all = [ 51 | ('getTestCasesForTestSuite', ['getkeywords=']), 52 | ('reportTCResult', ['user=', 'execduration=', 53 | 'timestamp=', 'steps=', 54 | "[{'step_number' : 6,"]), 55 | ('getLastExecutionResult', ['options=','getBugs']), 56 | ('getTestCasesForTestPlan', [',', 57 | 'buildid=', 'platformid=', 58 | 'testcaseid=', 'keywordid=', 59 | 'keywords=', 'executed=', 60 | 'assignedto=', 'executestatus=', 61 | 'executiontype=', 'getstepinfo=', 62 | 'details=
', 'customfields=', 63 | 'keywordid - keywords']), 64 | ('createTestCase', [',', ',', ',', 65 | ',', ',', #',', 66 | 'preconditions=', 67 | 'importance=', 68 | 'executiontype=', 'order=', 69 | 'internalid=', 70 | 'checkduplicatedname=', 71 | 'actiononduplicatedname=', 72 | 'status=', 73 | 'estimatedexecduration=']), 74 | ('createTestPlan', ['prefix=', 'testprojectname=']), 75 | ('getTestSuite',['', '']), 76 | ('updateTestSuite',[',', 'testprojectid=', 77 | 'prefix=', 'parentid=', 78 | 'testsuitename=', 'details=
', 79 | 'order=']), 80 | ('createBuild',[',', ',', 'active=', 81 | 'copytestersfrombuild=']), 82 | ('addTestCaseToTestPlan',[',', ',', 83 | ',', ',', 84 | 'platformid=', 85 | 'executionorder=', 86 | 'urgency=', 'overwrite=']), 87 | ('createTestProject',[',', ',', 88 | 'notes=', 'active=', 89 | 'public=', 'options=', 90 | 'itsname=', 'itsenabled=']), 91 | ('getIssueTrackerSystem',[',']), 92 | ('getExecutionSet',[',', 'testcaseid=', 93 | 'testcaseexternalid=', 94 | 'buildid=', 'buildname=', 95 | 'platformid=', 96 | 'platformname=', 'options=']), 97 | ('getRequirements',[',', 'testplanid=', 98 | 'platformid=']), 99 | ('getReqCoverage',[',', ',']), 100 | ('setTestCaseTestSuite',[',', ',']), 101 | ('getTestSuiteAttachments',[',']), 102 | ('getAllExecutionsResults',[',','testcaseid=', 103 | 'testcaseexternalid=', 104 | 'platformid=', 'buildid=', 105 | 'options=']), 106 | ('getTestCaseAttachments',['version=', 107 | 'testcaseexternalid=']), 108 | ('uploadTestCaseAttachment',[',', ',', 109 | 'title=', 'description=<description>', 110 | 'filename=<filename>', 'filetype=<filetype>', 111 | 'content=<content>']), 112 | ('createPlatform',['<testprojectname>,', '<platformname>,', 'notes=<notes>', 113 | 'platformondesign=<platformondesign>', 114 | 'platformonexecution=<platformonexecution>']), 115 | ('closeBuild', ['<buildid>']), 116 | ('createUser', ['<login>', '<firstname>', '<lastname>', '<email>', 117 | 'password=<password>']), 118 | ('setUserRoleOnProject', ['<userid>', '<rolename>', '<testprojectid>']) 119 | ] 120 | 121 | @pytest.mark.parametrize("apiCall, descriptions", 122 | test_data_apiCall_descriptions_equal_all) 123 | def test_whatArgs_apiCall_descriptions_equal_all(api_client, apiCall, descriptions): 124 | argsDescription = api_client.whatArgs(apiCall) 125 | for parts in descriptions: 126 | assert parts in argsDescription 127 | 128 | test_data_apiCall_descriptions_only_generic = [ 129 | ('createTestCase', ['<steps>,']), 130 | ('createBuild',['buildnotes=<buildnotes>']), 131 | ('getTestCaseAttachments',['testcaseid=<testcaseid>']) 132 | ] 133 | @pytest.mark.parametrize("apiCall, descriptions", 134 | test_data_apiCall_descriptions_only_generic) 135 | def test_whatArgs_apiCall_descriptions_only_generic(api_generic_client, apiCall, descriptions): 136 | argsDescription = api_generic_client.whatArgs(apiCall) 137 | for parts in descriptions: 138 | assert parts in argsDescription 139 | 140 | test_data_apiCall_descriptions_only_general = [ 141 | ('createTestCase', ['steps=<steps>']), 142 | ('createBuild',['<buildnotes>,']), 143 | ('getTestCaseAttachments',['<testcaseid>,']) 144 | ] 145 | @pytest.mark.parametrize("apiCall, descriptions", 146 | test_data_apiCall_descriptions_only_general) 147 | def test_whatArgs_apiCall_descriptions_only_general(api_general_client, apiCall, descriptions): 148 | argsDescription = api_general_client.whatArgs(apiCall) 149 | for parts in descriptions: 150 | assert parts in argsDescription 151 | 152 | -------------------------------------------------------------------------------- /src/testlink/testlinkdecorators.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | from functools import wraps 21 | from .testlinkargs import registerMethod, registerArgOptional, registerArgNonApi 22 | from .testlinkerrors import TLResponseError 23 | 24 | __doc__ = """ This internal module defines the decorator functions, which are 25 | used to generate the TestLink API methods in a generic way 26 | 27 | Method definitions should be build either with 28 | @decoMakerApiCallWithArgs(argNamesPositional, argNamesOptional) 29 | for calling a server method with arguments 30 | - argNamesPositional = list default positional args 31 | - argNamesOptional = list additional optional args 32 | to check the server response, if it includes TestLink Error Codes or 33 | an empty result (which raise a TLResponseError) 34 | or 35 | @decoApiCallWithoutArgs 36 | for calling server methods without arguments 37 | to check the server response, if it includes TestLink Error Codes or 38 | an empty result (which raise a TLResponseError) 39 | 40 | Additional behavior could be added with 41 | 42 | @decoApiCallAddDevKey 43 | - to expand the parameter list with devKey key/value pair 44 | before calling the server method 45 | @decoMakerApiCallReplaceTLResponseError(replaceCode) 46 | - to catch an TLResponseError after calling the server method and with an 47 | empty list 48 | - replaceCode : TestLink Error Code, which should be handled 49 | default is None for "Empty Results" 50 | @decoApiCallAddAttachment: 51 | - to add an mandatory argument 'attachmentfile' 52 | - attachmentfile is a python file descriptor pointing to the file 53 | - to expand parameter list with key/value pairs 54 | 'filename', 'filetype', 'content' 55 | from 'attachmentfile' before calling the server method 56 | @decoMakerApiCallChangePosToOptArg(argPos, argName): 57 | - to change the positional argument ARGPOS into an optional argument ARGNAME. 58 | - used for example in createTestPlan to support TL 1.9.13 and 1.9.14 59 | argument definitions 60 | - with TL 1.9.13 'testprojectname' was mandatory and expected as argPos 2 61 | - with TL 1.9.14 'testproject' is optional, cause new argument 'prefix' 62 | could be used as alternative 63 | """ 64 | 65 | 66 | 67 | # decorators for generic api calls 68 | # see http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python 69 | 70 | def decoApiCallWithoutArgs(methodAPI): 71 | """ Decorator for calling server methods without arguments """ 72 | 73 | # register methods without positional and optional arguments 74 | registerMethod(methodAPI.__name__) 75 | 76 | @wraps(methodAPI) 77 | def wrapperWithoutArgs(self): 78 | return self.callServerWithPosArgs(methodAPI.__name__) 79 | return wrapperWithoutArgs 80 | 81 | def decoMakerApiCallWithArgs(argNamesPositional=[], argNamesOptional=[]): 82 | """ creates a decorator for calling a server method with arguments 83 | 84 | argNamesPositional defines a list of positional arguments, which should be 85 | registered in the global apiMethodsArgNames for the server method 86 | argNamesOptional defines a list of optional arguments, which should be 87 | registered in the global apiMethodsArgNames for the server method 88 | 89 | """ 90 | 91 | def decoApiCallWithArgs(methodAPI): 92 | """ Decorator for calling a server method with arguments """ 93 | 94 | # register methods positional and optional arguments 95 | registerMethod(methodAPI.__name__, argNamesPositional, argNamesOptional) 96 | # define the method server call 97 | @wraps(methodAPI) 98 | def wrapperWithArgs(self, *argsPositional, **argsOptional): 99 | return self.callServerWithPosArgs(methodAPI.__name__, 100 | *argsPositional, **argsOptional) 101 | return wrapperWithArgs 102 | return decoApiCallWithArgs 103 | 104 | def decoApiCallAddDevKey(methodAPI): 105 | """ Decorator to expand parameter list with devKey""" 106 | # register additional optional argument devKey 107 | registerArgOptional(methodAPI.__name__, 'devKey') 108 | @wraps(methodAPI) 109 | def wrapperAddDevKey(self, *argsPositional, **argsOptional): 110 | if not ('devKey' in argsOptional): 111 | argsOptional['devKey'] = self.devKey 112 | return methodAPI(self, *argsPositional, **argsOptional) 113 | return wrapperAddDevKey 114 | 115 | def decoMakerApiCallReplaceTLResponseError(replaceCode=None, replaceValue=[]): 116 | """ creates a decorator, which replace an TLResponseError with a new value 117 | 118 | Default (replaceCode=None) handles the cause 'Empty Result' 119 | - ok for getProjectTestPlans, getBuildsForTestPlan, which returns just '' 120 | Problems are getTestPlanByName, getFirstLevelTestSuitesForTestProject 121 | - they do not return just '', they returns the error message 122 | 3041: Test plan (noPlatform) has no platforms linked 123 | 7008: Test Project (noSuite) is empty 124 | could be handled with replaceCode=3041 / replaceCode=7008 125 | 126 | Default (replaceValue=[]) new return value is an empty list 127 | - could be changed to other things like {} 128 | 129 | """ 130 | # for understanding, what we are doing here please read 131 | # # see http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python 132 | # - Passing arguments to the decorator 133 | 134 | def decoApiCallReplaceTLResponseError(methodAPI): 135 | """ Decorator to replace an TLResponseError with an empty list """ 136 | @wraps(methodAPI) 137 | def wrapperReplaceTLResponseError(self, *argsPositional, **argsOptional): 138 | response = None 139 | try: 140 | response = methodAPI(self, *argsPositional, **argsOptional) 141 | except TLResponseError as tl_err: 142 | if tl_err.code == replaceCode: 143 | # empty result (response == '') -> code == None 144 | # special case NoPlatform -> code == 3041 145 | response = replaceValue 146 | else: 147 | # seems to be another response failure - we forward it 148 | raise 149 | return response 150 | return wrapperReplaceTLResponseError 151 | return decoApiCallReplaceTLResponseError 152 | 153 | def decoApiCallAddAttachment(methodAPI): 154 | """ Decorator to expand parameter list with devKey and attachmentfile 155 | attachmentfile is a python file descriptor pointing to the file 156 | """ 157 | registerArgOptional(methodAPI.__name__, 'devKey') 158 | registerArgNonApi(methodAPI.__name__, 'attachmentfile') 159 | @wraps(methodAPI) 160 | def wrapperAddAttachment(self, attachmentfile, *argsPositional, **argsOptional): 161 | if not ('devKey' in argsOptional): 162 | argsOptional['devKey'] = self.devKey 163 | argsAttachment = self._getAttachmentArgs(attachmentfile) 164 | # add additional key/value pairs from argsOptional 165 | # although overwrites filename, filetype, content with user definition 166 | # if they exist 167 | argsAttachment.update(argsOptional) 168 | return methodAPI(self, *argsPositional, **argsAttachment) 169 | return wrapperAddAttachment 170 | 171 | def decoMakerApiCallChangePosToOptArg(argPos, argName): 172 | """ creates a decorator, which change the positional argument ARGPOS into 173 | an optional argument ARGNAME. 174 | 175 | argPos=1 is the first positional arg 176 | """ 177 | # for understanding, what we are doing here please read 178 | # # see http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python 179 | # - Passing arguments to the decorator 180 | 181 | def decoApiCallChangePosToOptArg(methodAPI): 182 | """ Decorator to change a positional argument into an optional ARGNAME """ 183 | @wraps(methodAPI) 184 | def wrapperChangePosToOptArg(self, *argsPositional, **argsOptional): 185 | argsPositionalChanged = list(argsPositional) 186 | if (argPos > 0) and (len(argsPositional) >= argPos): 187 | argValue = argsPositionalChanged.pop(argPos - 1) 188 | argsOptional[argName] = argValue 189 | return methodAPI(self, *argsPositionalChanged, **argsOptional) 190 | return wrapperChangePosToOptArg 191 | return decoApiCallChangePosToOptArg 192 | -------------------------------------------------------------------------------- /doc/usage.rst: -------------------------------------------------------------------------------- 1 | TestLink-API-Python-client Usage 2 | ================================ 3 | 4 | .. contents:: 5 | :local: 6 | 7 | How to talk with TestLink in a python shell 8 | ------------------------------------------- 9 | 10 | Connect TestLink, count existing projects and get test case data: :: 11 | 12 | [PYENV]\testlink\Scripts\activate 13 | set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php 14 | set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink] 15 | python 16 | >>> import testlink 17 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 18 | >>> tls.countProjects() 19 | 3 20 | >>> tls.getTestCase(None, testcaseexternalid='NPROAPI3-1') 21 | [{'full_tc_external_id': 'NPROAPI3-1', 'node_order': '0', 'is_open': '1', 'id': '2757', ...}] 22 | 23 | Ask the TestLink API Client, what arguments a API method expects: :: 24 | 25 | import testlink 26 | tlh = testlink.TestLinkHelper() 27 | tls = tlh.connect(testlink.TestlinkAPIClient) 28 | print tls.whatArgs('createTestPlan') 29 | > createTestPlan(<testplanname>, <testprojectname>, [note=<note>], [active=<active>], 30 | [public=<public>], [devKey=<devKey>]) 31 | > create a test plan 32 | 33 | or generate a description of all implemented API method: :: 34 | 35 | import testlink 36 | tlh = testlink.TestLinkHelper() 37 | tls = tlh.connect(testlink.TestlinkAPIClient) 38 | for m in testlink.testlinkargs._apiMethodsArgs.keys(): 39 | print(tls.whatArgs(m), '\n') 40 | 41 | Copy test cases 42 | --------------- 43 | 44 | Copy an existing test case into another test suite with changed name:: 45 | 46 | >>> import testlink 47 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 48 | >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3') 49 | [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2', 50 | 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}] 51 | >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], testsuiteid=newSuiteID, 52 | testcasename='a new test case name') 53 | 54 | Create a new test case version with changed summary and importance:: 55 | 56 | >>> import testlink 57 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 58 | >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3') 59 | [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2', 60 | 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}] 61 | >>> tls.copyTCnewVersion(tc_info[0]['testcase_id'], summary='new summary', 62 | importance='1') 63 | 64 | Default is, that the last version of a test case is used for the copy. 65 | If a different version should be used, specify the required version as second 66 | argument. Examples:: 67 | 68 | >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], 1, testsuiteid=newSuiteID, 69 | testcasename='a new test case name') 70 | >>> tls.copyTCnewVersion(tc_info[0]['testcase_id'], 1, summary='new summary', 71 | importance='1') 72 | 73 | Report test results 74 | ------------------- 75 | 76 | Using the TestLink API Client - failed test case with none default reporter 77 | (argument 'users' usable with TL >= 1.9.10): 78 | 79 | >>> import testlink 80 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 81 | >>> tls.reportTCResult(a_TestCaseID, a_TestPlanID, 'a build name', 'f', 82 | 'some notes', 83 | user='a user login name', platformid=a_platformID) 84 | 85 | Using the TestLink Generic API Client - passed test case with none default 86 | reporter: 87 | 88 | >>> import testlink 89 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIGeneric) 90 | >>> tls.reportTCResult(a_TestPlanID, 'p', testcaseid=a_TestCaseID, 91 | buildname='a build name', notes='some notes', 92 | user='a login name', platformid=a_platformID) 93 | 94 | Using the TestLink Generic API Client - blocked test case with 95 | alternative optional args, default reporter (user for devKey) 96 | 97 | >>> import testlink 98 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIGeneric) 99 | >>> exTCID = tls.getTestCase(testcaseid=a_TestCaseID)[0]['full_tc_external_id'] 100 | >>> tls.reportTCResult(a_TestPlanID, 'b', testcaseexternalid=exTCID, 101 | buildid='a build name', platformname='a platform name') 102 | 103 | Report test results with timestamp and step result 104 | -------------------------------------------------- 105 | 106 | This test result uses the external test case id and not the internal. 107 | 108 | - argument 'execduration' and 'timestamp' usable with TL >= 1.9.14: 109 | - argument 'steps' usable with TL >= 1.9.15: 110 | 111 | >>> import testlink 112 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 113 | >>> tls.reportTCResult(None, newTestPlanID_A, None, 'f', '', guess=True, 114 | testcaseexternalid=tc_aa_full_ext_id, platformname=NEWPLATFORM_A, 115 | execduration=3.9, timestamp='2015-09-18 14:33', 116 | steps=[{'step_number' : 6, 'result' : 'p', 'notes' : 'result note for passed step 6'}, 117 | {'step_number' : 7, 'result' : 'f', 'notes' : 'result note for failed step 7'}] ) 118 | 119 | Upload attachments 120 | ------------------ 121 | 122 | uploading attachments can be done in two different ways 123 | 124 | with a file descriptor:: 125 | 126 | a_file_obj=open(A_VALID_FILE_PATH) 127 | newAttachment = myTestLink.uploadExecutionAttachment(a_file_obj, A_Result_ID, 128 | 'Attachment Title', 'Attachment Description') 129 | 130 | 131 | with a file path:: 132 | 133 | a_file_path=A_VALID_FILE_PATH 134 | newAttachment = myTestLink.uploadExecutionAttachment(a_file_path, A_Result_ID, 135 | 'Attachment Title', 'Attachment Description') 136 | 137 | List keywords 138 | ------------- 139 | 140 | Using the api method - keywords for all test cases of one test suite 141 | 142 | >>> import testlink 143 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 144 | >>> ts_kw = tls.getTestCasesForTestSuite(SuiteID, False, 'full', getkeywords=True) 145 | 146 | Using the api method - keywords for all test cases of a test suite and their 147 | sub suites 148 | 149 | >>> ts_kw = tls.getTestCasesForTestSuite(SuiteID, True, 'full', getkeywords=True) 150 | 151 | Using the service method - keyword list without internal details for one test case 152 | 153 | >>> tc_kw = tls.listKeywordsForTC(5440) 154 | >>> tc_kw = tls.listKeywordsForTC('NPROAPI-3') 155 | 156 | Using the service method - keyword lists without internal details for all test 157 | cases of one test suite 158 | 159 | >>> ts_kw = tls.listKeywordsForTS('5415') 160 | 161 | Run examples 162 | ------------ 163 | 164 | Running example, how to use the class TestlinkAPIClient, with connection 165 | parameter defined as command line arguments [1]_: :: 166 | 167 | [PYENV]\testlink\Scripts\activate 168 | python example\TestLinkExample.py 169 | --server_url http://[YOURSERVER]/testlink/lib/api/xmlrpc.php 170 | --devKey [Users devKey generated by TestLink] 171 | 172 | Running example, how to use the class TestlinkAPIGeneric, with connection 173 | parameter defined as environment variables [2]_: :: 174 | 175 | [PYENV]\testlink\Scripts\activate 176 | set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php 177 | set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink] 178 | python example\TestLinkExampleGenericApi.py 179 | 180 | .. [1] TestLinkExample.py creates a new test project NEW_PROJECT_API-[CountProjects+1]. 181 | .. [2] TestLinkExampleGenericApi.py creates a new test project PROJECT_API_GENERIC-[CountProjects+1]. 182 | 183 | Run unittests 184 | ------------- 185 | 186 | Run unittests with TestLink Server interaction: :: 187 | 188 | [PYENV]\testlink\Scripts\activate 189 | set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc.php 190 | set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink] 191 | cd test\utest 192 | python -m unittest discover -s test\utest-online 193 | 194 | Run unittests without TestLink Server interaction: :: 195 | 196 | [PYENV]\testlink\Scripts\activate 197 | cd test\utest 198 | python -m unittest discover -s test\utest-offline 199 | 200 | Under Py26, unittest2_ must be used. 201 | 202 | .. _unittest2: https://pypi.python.org/pypi/unittest2 203 | 204 | 205 | How to check original exchanged XML data 206 | ------------------------------------------ 207 | 208 | If for debugging reasons the original exchanged XML data are needed, initialise 209 | the API client with the optional argument *verbose* set to *True*: :: 210 | 211 | >>> tlh = testlink.TestLinkHelper() 212 | >>> tls = testlink.TestlinkAPIClient(tlh._server_url, tl._devkey, verbose=True) 213 | send: b"POST /testlink/lib/api/xmlrpc/v1/xmlrpc.php HTTP/1.1\r\nHost: ... 214 | <?xml version='1.0'?>\n<methodCall>\n<methodName>tl.getUserByLogin</methodName>\n<params>...</params>\n</methodCall>\n" 215 | reply: 'HTTP/1.1 200 OK\r\n' 216 | header: Date header: Server header: ... body: b'<?xml version="1.0"?>\n<methodResponse>\n <params> ...' 217 | body: b'</name><value><string>1</string></value></member>\n</struct></value>\n <value><struct>\n ...' 218 | body: b'... </params>\n</methodResponse>\n' 219 | 220 | 221 | -------------------------------------------------------------------------------- /test/utest-offline/testlinkhelper_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2012-2019 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | # TestCases for TestLinkHelper 21 | # this test works WITHOUT an online TestLink Server 22 | # no calls are send to a TestLink Server 23 | 24 | import pytest, sys 25 | IS_PY3 = sys.version_info[0] > 2 26 | 27 | class DummyTestLinkAPI(object): 28 | """ Dummy for Simulation TestLinkAPICLient. 29 | Used for init() tests with TestLinkHelper.connect(api_class) 30 | """ 31 | 32 | def __init__(self, server_url, devKey, **args): 33 | self.server = server_url 34 | self.devKey = devKey 35 | self.args = args 36 | 37 | ENVNAMES = ('TESTLINK_API_PYTHON_SERVER_URL', 'TESTLINK_API_PYTHON_DEVKEY', 38 | 'http_proxy') 39 | EXPECTED_DEFAULTS = ('http://localhost/testlink/lib/api/xmlrpc.php', '42', 40 | '') 41 | 42 | def setEnviron(monkeypatch, envname, envvalue ): 43 | """ manipulates environment variable ENVNAME to emulate setting ENVVALUE. 44 | tests must use pytest fixture monkeypatch """ 45 | if envvalue is None: 46 | monkeypatch.delenv(envname, False) 47 | else: 48 | monkeypatch.setenv(envname, envvalue) 49 | 50 | 51 | test_data_init_envs = [ 52 | ((None, None, None), 53 | EXPECTED_DEFAULTS ), 54 | (('SERVER-URL-1', None, None), 55 | ('SERVER-URL-1', EXPECTED_DEFAULTS[1], EXPECTED_DEFAULTS[2])), 56 | ((None, 'DEVKEY-2', None), 57 | (EXPECTED_DEFAULTS[0], 'DEVKEY-2', EXPECTED_DEFAULTS[2])), 58 | (('SERVER-URL-3', 'DEVKEY-3', None), 59 | ('SERVER-URL-3', 'DEVKEY-3', EXPECTED_DEFAULTS[2])), 60 | ((None, None, 'Proxy-4'), 61 | (EXPECTED_DEFAULTS[0], EXPECTED_DEFAULTS[1], 'Proxy-4')), 62 | (('SERVER-URL-5', 'DEVKEY-5', 'Proxy-5'), 63 | ('SERVER-URL-5', 'DEVKEY-5', 'Proxy-5')) 64 | ] 65 | 66 | @pytest.mark.parametrize("env_values, expectations", test_data_init_envs) 67 | def test_init_env(api_helper_class, monkeypatch, env_values, expectations ): 68 | """ init TestLinkHelper with environment variables """ 69 | # set TestLinkHelper related environment variables 70 | setEnviron(monkeypatch, ENVNAMES[0], env_values[0]) 71 | setEnviron(monkeypatch, ENVNAMES[1], env_values[1]) 72 | setEnviron(monkeypatch, ENVNAMES[2], env_values[2]) 73 | # init helper without params 74 | a_helper = api_helper_class() 75 | assert expectations == (a_helper._server_url, a_helper._devkey, 76 | a_helper._proxy) 77 | 78 | 79 | test_data_init_params = [ 80 | (('SERVER-URL-11', None, None), 81 | ('SERVER-URL-11', EXPECTED_DEFAULTS[1], EXPECTED_DEFAULTS[2])), 82 | ((None, 'DEVKEY-12', None), 83 | (EXPECTED_DEFAULTS[0], 'DEVKEY-12', EXPECTED_DEFAULTS[2])), 84 | (('SERVER-URL-13', 'DEVKEY-13', None), 85 | ('SERVER-URL-13', 'DEVKEY-13', EXPECTED_DEFAULTS[2])), 86 | ((None, None, 'Proxy-14'), 87 | (EXPECTED_DEFAULTS[0], EXPECTED_DEFAULTS[1],'Proxy-14')), 88 | (('SERVER-URL-15', 'DEVKEY-15', 'Proxy-15'), 89 | ('SERVER-URL-15', 'DEVKEY-15', 'Proxy-15')) 90 | ] 91 | 92 | @pytest.mark.parametrize("param_values, expectations", test_data_init_params) 93 | def test_init_params(api_helper_class, monkeypatch, param_values, expectations): 94 | """ init TestLinkHelper with parameter and no env variables """ 95 | # unset TestLinkHelper related environment variables 96 | setEnviron(monkeypatch, ENVNAMES[0], None) 97 | setEnviron(monkeypatch, ENVNAMES[1], None) 98 | setEnviron(monkeypatch, ENVNAMES[2], None) 99 | # init helper with params 100 | a_helper = api_helper_class(*param_values) 101 | assert expectations == (a_helper._server_url, a_helper._devkey, 102 | a_helper._proxy) 103 | 104 | def test_init_env_params(api_helper_class, monkeypatch): 105 | """ init TestLinkHelper with mixed method parameter and env variables """ 106 | # set TestLinkHelper related environment variables 107 | setEnviron(monkeypatch, ENVNAMES[0], 'SERVER-URL-21') 108 | setEnviron(monkeypatch, ENVNAMES[1], 'DEVKEY-21') 109 | setEnviron(monkeypatch, ENVNAMES[2], 'PROXY-21') 110 | # init helper with method params 111 | a_helper = api_helper_class('SERVER-URL-22', 'DEVKEY-22', 'PROXY-22') 112 | # the method params have a high priority than the environment variables 113 | assert 'SERVER-URL-22' == a_helper._server_url 114 | assert 'DEVKEY-22' == a_helper._devkey 115 | assert 'PROXY-22' == a_helper._proxy 116 | 117 | 118 | def test_createArgparser(api_helper_class): 119 | """ create TestLinkHelper command line argument parser """ 120 | a_helper = api_helper_class('SERVER-URL-31', 'DEVKEY-31', 'PROXY-31') 121 | a_parser = a_helper._createArgparser('DESCRIPTION-31') 122 | assert 'DESCRIPTION-31', a_parser.description 123 | default_args=a_parser.parse_args('') 124 | assert 'SERVER-URL-31' == default_args.server_url 125 | assert 'DEVKEY-31' == default_args.devKey 126 | assert 'PROXY-31' == default_args.proxy 127 | 128 | def test_setParamsFromArgs(api_helper_class): 129 | """ set TestLinkHelper params from command line arguments """ 130 | a_helper = api_helper_class() 131 | a_helper.setParamsFromArgs(None, ['--server_url', 'SERVER-URL-41', 132 | '--devKey' , 'DEVKEY-41']) 133 | assert 'SERVER-URL-41' == a_helper._server_url 134 | assert 'DEVKEY-41' == a_helper._devkey 135 | 136 | def test_connect(api_helper_class): 137 | """ create a TestLink API dummy """ 138 | a_helper = api_helper_class('SERVER-URL-51', 'DEVKEY-51') 139 | a_tl_api = a_helper.connect(DummyTestLinkAPI) 140 | assert 'SERVER-URL-51' == a_tl_api.server 141 | assert 'DEVKEY-51' == a_tl_api.devKey 142 | assert {} == a_tl_api.args 143 | 144 | def test_getProxiedTransport_py2(api_helper_class): 145 | """ create a TestLink Helper with ProxiedTransport - py27 """ 146 | if IS_PY3: 147 | pytest.skip("py27 specific test") 148 | 149 | a_helper = api_helper_class('SERVER-URL-611', 'DEVKEY-611', 'PROXY-611:8080') 150 | #'http://fast.proxy.com.de/') 151 | a_pt = a_helper._getProxiedTransport() 152 | assert 'ProxiedTransport' == a_pt.__class__.__name__ 153 | assert 'PROXY-611:8080' == a_pt.proxy 154 | 155 | def test_getProxiedTransport_py3(api_helper_class): 156 | """ create a TestLink Helper with ProxiedTransport - py3x """ 157 | 158 | if not IS_PY3: 159 | pytest.skip("py3 specific test") 160 | 161 | a_helper = api_helper_class('SERVER-URL-612', 'DEVKEY-612', 'http://PROXY-612:8080') 162 | #'http://fast.proxy.com.de/') 163 | a_pt = a_helper._getProxiedTransport() 164 | assert 'ProxiedTransport' == a_pt.__class__.__name__ 165 | assert ('http://proxy-612', 8080) == a_pt.proxy 166 | 167 | def test_connect_with_proxy2(api_helper_class): 168 | """ create a TestLink API dummy with ProxiedTransport - py27""" 169 | if IS_PY3: 170 | pytest.skip("py27 specific test") 171 | 172 | a_helper = api_helper_class('SERVER-URL-711', 'DEVKEY-711', 'PROXY-711:8080') 173 | a_tl_api = a_helper.connect(DummyTestLinkAPI) 174 | assert 'SERVER-URL-711' == a_tl_api.server 175 | assert 'DEVKEY-711' == a_tl_api.devKey 176 | assert 'PROXY-711:8080' == a_tl_api.args['transport'].proxy 177 | 178 | def test_connect_with_proxy3(api_helper_class): 179 | """ create a TestLink API dummy with ProxiedTransport - py3x""" 180 | if not IS_PY3: 181 | pytest.skip("py3 specific test") 182 | 183 | a_helper = api_helper_class('SERVER-URL-712', 'DEVKEY-712', 'https://PROXY-712:8080') 184 | a_tl_api = a_helper.connect(DummyTestLinkAPI) 185 | assert 'SERVER-URL-712' == a_tl_api.server 186 | assert 'DEVKEY-712' == a_tl_api.devKey 187 | assert ('https://proxy-712', 8080) == a_tl_api.args['transport'].proxy 188 | 189 | def test_connect_ignoring_proxy_env(api_helper_class, monkeypatch): 190 | """ create a TestLink API dummy ignoring PROXY env - pullRequest #121 """ 191 | setEnviron(monkeypatch, ENVNAMES[2], 'PROXY-71') 192 | a_helper = api_helper_class('SERVER-URL-71', 'DEVKEY-71', False) 193 | a_tl_api = a_helper.connect(DummyTestLinkAPI) 194 | assert 'SERVER-URL-71' == a_tl_api.server 195 | assert 'DEVKEY-71' == a_tl_api.devKey 196 | assert {} == a_tl_api.args 197 | 198 | 199 | def test_connect_with_https_no_context(api_helper_class): 200 | """ create a TestLink API dummy for https with uncertified context """ 201 | a_helper = api_helper_class('https://SERVER-URL-71', 'DEVKEY-71') 202 | a_tl_api = a_helper.connect(DummyTestLinkAPI) 203 | assert 'https://SERVER-URL-71' == a_tl_api.server 204 | assert 'DEVKEY-71' == a_tl_api.devKey 205 | a_context = a_tl_api.args['context'] 206 | assert [] == a_context.get_ca_certs() 207 | 208 | def test_connect_with_https_and_context(api_helper_class): 209 | """ create a TestLink API dummy for https with special context """ 210 | a_helper = api_helper_class('https://SERVER-URL-71', 'DEVKEY-71') 211 | a_tl_api = a_helper.connect(DummyTestLinkAPI, context='ssl_context') 212 | assert 'https://SERVER-URL-71' == a_tl_api.server 213 | assert 'DEVKEY-71' == a_tl_api.devKey 214 | assert 'ssl_context' == a_tl_api.args['context'] 215 | -------------------------------------------------------------------------------- /doc/fr_usage.rst: -------------------------------------------------------------------------------- 1 | TestLink-API-Python-client Usage 2 | ================================ 3 | 4 | .. contents:: 5 | :local: 6 | 7 | Comment communiquer avec testlink dans une interface système python 8 | ------------------------------------------- 9 | 10 | Se connecter à TestLink, compter les projets existants et récupérer les données d'un cas de test: :: 11 | 12 | [PYENV]\testlink\Scripts\activate 13 | set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php 14 | set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink] 15 | python 16 | >>> import testlink 17 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 18 | >>> tls.countProjects() 19 | 3 20 | >>> tls.getTestCase(None, testcaseexternalid='NPROAPI3-1') 21 | [{'full_tc_external_id': 'NPROAPI3-1', 'node_order': '0', 'is_open': '1', 'id': '2757', ...}] 22 | 23 | Demander au TestLink API Client quels arguments attend une des méthodes de l'API: :: 24 | 25 | import testlink 26 | tlh = testlink.TestLinkHelper() 27 | tls = tlh.connect(testlink.TestlinkAPIClient) 28 | print tls.whatArgs('createTestPlan') 29 | > createTestPlan(<testplanname>, <testprojectname>, [note=<note>], [active=<active>], 30 | [public=<public>], [devKey=<devKey>]) 31 | > create a test plan 32 | 33 | Générer une description de toutes les méthodes implémentées par l'API: :: 34 | 35 | import testlink 36 | tlh = testlink.TestLinkHelper() 37 | tls = tlh.connect(testlink.TestlinkAPIClient) 38 | for m in testlink.testlinkargs._apiMethodsArgs.keys(): 39 | print(tls.whatArgs(m), '\n') 40 | 41 | Copier les cas de test 42 | --------------- 43 | 44 | Copier un cas de test dans une autre suite en changeant son nom:: 45 | 46 | >>> import testlink 47 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 48 | >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3') 49 | [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2', 50 | 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}] 51 | >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], testsuiteid=newSuiteID, 52 | testcasename='a new test case name') 53 | 54 | Créer une nouvelle version d'un cas de test en changeant sa description et son importance:: 55 | 56 | >>> import testlink 57 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 58 | >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3') 59 | [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2', 60 | 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}] 61 | >>> tls.copyTCnewVersion(tc_info[0]['testcase_id'], summary='new summary', 62 | importance='1') 63 | 64 | 65 | Par défaut, la dernière version d'un cas de test sera utilisée pour la copie. 66 | Si une autre version doit être copiée, il est possible de spécifier la version 67 | attendue en tant que deuxième argument. Example:: 68 | 69 | >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], 1, testsuiteid=newSuiteID, 70 | testcasename='a new test case name') 71 | >>> tls.copyTCnewVersion(tc_info[0]['testcase_id'], 1, summary='new summary', 72 | importance='1') 73 | 74 | Rapporter les résultats du test 75 | ------------------- 76 | 77 | En utilisant la classe TestlinkAPIClient - exemple d'un cas de test échoué 78 | sans auteur (l'argument 'user' n'est utilisable qu'à partir d'une version 79 | de TestLink de 1.9.10 ou supérieure): 80 | 81 | >>> import testlink 82 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 83 | >>> tls.reportTCResult(a_TestCaseID, a_TestPlanID, 'a build name', 'f', 84 | 'some notes', 85 | user='a user login name', platformid=a_platformID) 86 | 87 | 88 | En utilisant la classe TestlinkAPIGeneric - exemple d'un cas de test passé 89 | en utilisant un auteur (argument 'user'): 90 | 91 | >>> import testlink 92 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIGeneric) 93 | >>> tls.reportTCResult(a_TestPlanID, 'p', testcaseid=a_TestCaseID, 94 | buildname='a build name', notes='some notes', 95 | user='a login name', platformid=a_platformID) 96 | 97 | 98 | En utilisant la classe TestlinkAPIGeneric - exemple d'un cas de test bloqué 99 | sans auteur 100 | 101 | >>> import testlink 102 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIGeneric) 103 | >>> exTCID = tls.getTestCase(testcaseid=a_TestCaseID)[0]['full_tc_external_id'] 104 | >>> tls.reportTCResult(a_TestPlanID, 'b', testcaseexternalid=exTCID, 105 | buildid='a build name', platformname='a platform name') 106 | 107 | Rapport de résultats de tests avec horodatage et résultat des étapes 108 | -------------------------------------------------- 109 | 110 | Ce résultat de test utilise son id externe (testcaseexternalid), et non l'id interne (testcaseid) 111 | 112 | - Les arguments 'execduration' et 'timestamp' requièrent une version de 113 | TestLink de 1.9.14 ou supérieure 114 | - L'argument 'steps' requiert une version de TestLink de 1.9.15 ou supérieure 115 | 116 | >>> import testlink 117 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 118 | >>> tls.reportTCResult(None, newTestPlanID_A, None, 'f', '', guess=True, 119 | testcaseexternalid=tc_aa_full_ext_id, platformname=NEWPLATFORM_A, 120 | execduration=3.9, timestamp='2015-09-18 14:33', 121 | steps=[{'step_number' : 6, 'result' : 'p', 'notes' : 'result note for passed step 6'}, 122 | {'step_number' : 7, 'result' : 'f', 'notes' : 'result note for failed step 7'}] ) 123 | 124 | Envoyer des pièces jointes 125 | ------------------ 126 | 127 | Télécharger des pièces jointes peut être fait de deux différentes manières: 128 | 129 | Avec un descripteur de fichier : 130 | 131 | a_file_obj=open(CHEMIN_VALIDE_VERS_LE_FICHIER) 132 | newAttachment = myTestLink.uploadExecutionAttachment(a_file_obj, A_Result_ID, 133 | 'Attachment Title', 'Attachment Description') 134 | 135 | 136 | Ou avec un chemin de fichier : 137 | 138 | a_file_path=A_VALID_FILE_PATH 139 | newAttachment = myTestLink.uploadExecutionAttachment(CHEMIN_VALIDE_VERS_LE_FICHIER, A_Result_ID, 140 | 'Attachment Title', 'Attachment Description') 141 | 142 | Lister les mots-clés 143 | ------------- 144 | 145 | En utilisant une méthode de l'API (classe TestlinkAPIGeneric) - 146 | Lister les mots-clés de tous les cas de test d'une suite: 147 | 148 | >>> import testlink 149 | >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient) 150 | >>> ts_kw = tls.getTestCasesForTestSuite(SuiteID, False, 'full', getkeywords=True) 151 | 152 | 153 | En utilisant une méthode de l'API (classe TestlinkAPIGeneric) - 154 | Lister tous les mots clés d'une suite de test et ses sous-suites 155 | 156 | >>> ts_kw = tls.getTestCasesForTestSuite(SuiteID, True, 'full', getkeywords=True) 157 | 158 | En utilisant une méthode du service (classe TestlinkAPIClient) - 159 | Lister tous les mots clés sans ses détails pour un cas de test 160 | 161 | >>> tc_kw = tls.listKeywordsForTC(5440) 162 | >>> tc_kw = tls.listKeywordsForTC('NPROAPI-3') 163 | 164 | En utilisant une méthode du service (classe TestlinkAPIClient) - 165 | Lister tous les mots clés sans ses détails pour tous les cas de test d'une suite 166 | 167 | >>> ts_kw = tls.listKeywordsForTS('5415') 168 | 169 | 170 | Lancement d'un exemple 171 | ------------ 172 | 173 | Pour lancer l'exemple "comment utiliser la classe TestlinkAPIClient", en 174 | spécifiant les paramètres de connexion en tant qu'arguments de ligne de commande [1]_: :: 175 | 176 | [PYENV]\testlink\Scripts\activate 177 | python example\TestLinkExample.py 178 | --server_url http://[YOURSERVER]/testlink/lib/api/xmlrpc.php 179 | --devKey [Users devKey generated by TestLink] 180 | 181 | Pour lancer l'exemple "comment utiliser la classe TestlinkAPIGeneric", en 182 | spécifiant les paramètres de connexion en tant que variable d'environment [2]_: :: 183 | 184 | [PYENV]\testlink\Scripts\activate 185 | set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php 186 | set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink] 187 | python example\TestLinkExampleGenericApi.py 188 | 189 | .. [1] TestLinkExample.py creates a new test project NEW_PROJECT_API-[CountProjects+1]. 190 | .. [2] TestLinkExampleGenericApi.py creates a new test project PROJECT_API_GENERIC-[CountProjects+1]. 191 | 192 | Lancer des tests unitaires 193 | ------------- 194 | 195 | Lancer des tests unitaires avec interaction du serveur de TestLink: :: 196 | 197 | [PYENV]\testlink\Scripts\activate 198 | set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc.php 199 | set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink] 200 | cd test\utest 201 | python -m unittest discover -s test\utest-online 202 | 203 | Lancer des tests unitaires sans interaction du serveur de TestLink: :: 204 | 205 | [PYENV]\testlink\Scripts\activate 206 | cd test\utest 207 | python -m unittest discover -s test\utest-offline 208 | 209 | En deca de Py26, unittest2_ doit être utilisé. 210 | 211 | .. _unittest2: https://pypi.python.org/pypi/unittest2 212 | 213 | 214 | Comment accéder aux données originelles d'échange de XML 215 | ------------------------------------------ 216 | 217 | Si pour des raisons de débogage les versions originelles d'échange de XML sont requises, 218 | il est possible d'initialiser l'API client avec le paramètre optionnel *verbose* mis à *True*: :: 219 | 220 | >>> tlh = testlink.TestLinkHelper() 221 | >>> tls = testlink.TestlinkAPIClient(tlh._server_url, tl._devkey, verbose=True) 222 | send: b"POST /testlink/lib/api/xmlrpc/v1/xmlrpc.php HTTP/1.1\r\nHost: ... 223 | <?xml version='1.0'?>\n<methodCall>\n<methodName>tl.getUserByLogin</methodName>\n<params>...</params>\n</methodCall>\n" 224 | reply: 'HTTP/1.1 200 OK\r\n' 225 | header: Date header: Server header: ... body: b'<?xml version="1.0"?>\n<methodResponse>\n <params> ...' 226 | body: b'</name><value><string>1</string></value></member>\n</struct></value>\n <value><struct>\n ...' 227 | body: b'... </params>\n</methodResponse>\n' 228 | 229 | 230 | -------------------------------------------------------------------------------- /src/testlink/testreporter.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2017-2019 Brian-Willams, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | 21 | from testlink.testlinkerrors import TLResponseError, TLArgError 22 | 23 | 24 | class TestReporter(dict): 25 | def __init__(self, tls, testcases, *args, **kwargs): 26 | """This can be given one or more testcases, but they all must have the 27 | same project, plan, and platform. 28 | 29 | TESTCASES must be one or a list of full external testcase id 30 | TLS must be an instance of TestlinkAPIClient, defining a 31 | XMLRPC connection to a TestLink Server""" 32 | super(TestReporter, self).__init__(*args, **kwargs) 33 | self.tls = tls 34 | # handle single testcase 35 | self.testcases = testcases if isinstance(testcases, list) else [testcases] 36 | self._plan_testcases = None 37 | self.remove_non_report_kwargs() 38 | self._platformname_generated = False 39 | 40 | def remove_non_report_kwargs(self): 41 | self.buildname = self.pop('buildname') 42 | 43 | default_note = "created automatically with {}".format(self.__class__.__name__) 44 | self.buildnotes = self.pop('buildnotes', default_note) 45 | self.testplannotes = self.pop('testplannotes', default_note) 46 | self.platformnotes = self.pop('platformnotes', default_note) 47 | 48 | def setup_testlink(self): 49 | """Call properties that may set report kwarg values.""" 50 | self.testprojectname 51 | self.testprojectid 52 | self.testplanid 53 | self.testplanname 54 | self.platformname 55 | self.platformid 56 | self.buildid 57 | 58 | def _get_project_name_by_id(self): 59 | if self.testprojectid: 60 | for project in self.tls.getProjects(): 61 | if project['id'] == self.testprojectid: 62 | return project['name'] 63 | 64 | def _projectname_getter(self): 65 | if not self.get('testprojectname') and self.testprojectid: 66 | self['testprojectname'] = self._get_project_name_by_id() 67 | return self.get('testprojectname') 68 | 69 | @property 70 | def testprojectname(self): 71 | return self._projectname_getter() 72 | 73 | def _get_project_id(self): 74 | tpid = self.get('testprojectid') 75 | if not tpid and self.testprojectname: 76 | self['testprojectid'] = self.tls.getProjectIDByName(self['testprojectname']) 77 | return self['testprojectid'] 78 | return tpid 79 | 80 | def _get_project_id_or_none(self): 81 | project_id = self._get_project_id() 82 | # If not found the id will return as -1 83 | if project_id == -1: 84 | project_id = None 85 | return project_id 86 | 87 | @property 88 | def testprojectid(self): 89 | self['testprojectid'] = self._get_project_id_or_none() 90 | return self.get('testprojectid') 91 | 92 | @property 93 | def testplanid(self): 94 | return self.get('testplanid') 95 | 96 | @property 97 | def testplanname(self): 98 | return self.get('testplanname') 99 | 100 | @property 101 | def platformname(self): 102 | """Return a platformname added to the testplan if there is one.""" 103 | return self.get('platformname') 104 | 105 | @property 106 | def platformid(self): 107 | return self.get('platformid') 108 | 109 | @property 110 | def buildid(self): 111 | return self.get('buildid') 112 | 113 | @property 114 | def plan_tcids(self): 115 | if not self._plan_testcases: 116 | self._plan_testcases = set() 117 | tc_dict = self.tls.getTestCasesForTestPlan(self.testplanid) 118 | try: 119 | for _, platform in tc_dict.items(): 120 | for k, v in platform.items(): 121 | self._plan_testcases.add(v['full_external_id']) 122 | except AttributeError: 123 | # getTestCasesForTestPlan returns an empty list instead of an empty dict 124 | pass 125 | return self._plan_testcases 126 | 127 | def reportgen(self): 128 | """For use if you need to look at the status returns of individual reporting.""" 129 | self.setup_testlink() 130 | for testcase in self.testcases: 131 | yield self.tls.reportTCResult(testcaseexternalid=testcase, **self) 132 | 133 | def report(self): 134 | for _ in self.reportgen(): 135 | pass 136 | 137 | 138 | class AddTestCaseReporter(TestReporter): 139 | """Add testcase to testplan if not added.""" 140 | def setup_testlink(self): 141 | super(AddTestCaseReporter, self).setup_testlink() 142 | self.ensure_testcases_in_plan() 143 | 144 | def ensure_testcases_in_plan(self): 145 | # Get the platformid if possible or else addition will fail 146 | self.platformid 147 | for testcase in self.testcases: 148 | # Can't check if testcase is in plan_tcids, because that won't work if it's there, but of the wrong platform 149 | try: 150 | self.tls.addTestCaseToTestPlan( 151 | self.testprojectid, self.testplanid, testcase, self.get_latest_tc_version(testcase), 152 | platformid=self.platformid 153 | ) 154 | except TLResponseError as e: 155 | # Test Case version is already linked to Test Plan 156 | if e.code == 3045: 157 | pass 158 | else: 159 | raise 160 | 161 | def get_latest_tc_version(self, testcaseexternalid): 162 | return int(self.tls.getTestCase(None, testcaseexternalid=testcaseexternalid)[0]['version']) 163 | 164 | 165 | class AddTestPlanReporter(TestReporter): 166 | @property 167 | def testplanid(self): 168 | if not self.get('testplanid'): 169 | try: 170 | self['testplanid'] = self.tls.getTestPlanByName(self.testprojectname, self.testplanname)[0]['id'] 171 | except TLResponseError as e: 172 | # Name does not exist 173 | if e.code == 3033: 174 | self['testplanid'] = self._generate_testplanid() 175 | else: 176 | raise 177 | except TypeError: 178 | self['testplanid'] = self._generate_testplanid() 179 | return self['testplanid'] 180 | 181 | def _generate_testplanid(self): 182 | """This won't necessarily be able to create a testplanid. It requires a planname and projectname.""" 183 | if 'testplanname' not in self: 184 | raise TLArgError("Need testplanname to generate a testplan for results.") 185 | 186 | tp = self.tls.createTestPlan(self['testplanname'], self.testprojectname, 187 | notes=self.testplannotes) 188 | self['testplanid'] = tp[0]['id'] 189 | return self['testplanid'] 190 | 191 | 192 | class AddPlatformReporter(TestReporter): 193 | @property 194 | def platformname(self): 195 | """Return a platformname added to the testplan if there is one.""" 196 | pn_kwarg = self.get('platformname') 197 | if pn_kwarg and self._platformname_generated is False: 198 | # If we try to create platform and catch platform already exists error (12000) it sometimes duplicates a 199 | # platformname 200 | try: 201 | self.tls.addPlatformToTestPlan(self.testplanid, pn_kwarg) 202 | except TLResponseError as e: 203 | if int(e.code) == 235: 204 | self.tls.createPlatform(self.testprojectname, pn_kwarg, 205 | notes=self.platformnotes, 206 | platformondesign=True, platformonexecution=True) 207 | self.tls.addPlatformToTestPlan(self.testplanid, pn_kwarg) 208 | self._platformname_generated = True 209 | else: 210 | raise 211 | return pn_kwarg 212 | 213 | @property 214 | def platformid(self): 215 | if not self.get('platformid'): 216 | self['platformid'] = self.getPlatformID(self.platformname) 217 | # This action is idempotent 218 | self.tls.addPlatformToTestPlan(self.testplanid, self.platformname) 219 | return self['platformid'] 220 | 221 | def getPlatformID(self, platformname, _firstrun=True): 222 | """ 223 | This is hardcoded for platformname to always be self.platformname 224 | """ 225 | platforms = self.tls.getTestPlanPlatforms(self.testplanid) 226 | for platform in platforms: 227 | # https://github.com/Brian-Williams/TestLink-API-Python-client/issues/1 228 | if platform['name'].lower() == platformname.lower(): 229 | return platform['id'] 230 | # Platformname houses platform creation as platform creation w/o a name isn't possible 231 | if not self.platformname: 232 | raise TLArgError( 233 | "Couldn't find platformid for {}.{}, " 234 | "please provide a platformname to generate.".format(self.testplanid, self.platformname) 235 | ) 236 | if _firstrun is True: 237 | return self.getPlatformID(self.platformname, _firstrun=False) 238 | else: 239 | raise TLArgError("PlatformID not found after generated from platformname '{}' " 240 | "in test plan {}.".format(self.platformname, self.testplanid)) 241 | 242 | 243 | class AddBuildReporter(TestReporter): 244 | @property 245 | def buildid(self): 246 | bid = self.get('buildid') 247 | if not bid or bid not in self.tls.getBuildsForTestPlan(self.testplanid): 248 | self['buildid'] = self._generate_buildid() 249 | return self.get('buildid') 250 | 251 | def _generate_buildid(self): 252 | r = self.tls.createBuild(self.testplanid, self.buildname, self.buildnotes) 253 | return r[0]['id'] 254 | -------------------------------------------------------------------------------- /test/utest-offline/testlinkdecorators_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | # this test works WITHOUT an online TestLink Server 21 | # no calls are send to a TestLink Server 22 | # 23 | # TestCases for decorators, used by TestlinkAPIGeneric building 24 | # TestLink API methods. 25 | 26 | import pytest 27 | 28 | from testlink.testlinkerrors import TLResponseError 29 | from testlink.testlinkargs import registerMethod, getArgsForMethod 30 | from testlink.testlinkdecorators import decoApiCallAddAttachment,\ 31 | decoApiCallAddDevKey, decoApiCallWithoutArgs, \ 32 | decoMakerApiCallReplaceTLResponseError, decoMakerApiCallWithArgs, \ 33 | decoMakerApiCallChangePosToOptArg 34 | 35 | 36 | class dummy_api_testlinkdecorator(object): 37 | """ class simulating testlink api client with required attributes for 38 | testlinkdecorators_test """ 39 | 40 | devKey = '007' 41 | 42 | def _getAttachmentArgs(self, attachmentfile): 43 | # simulation of TestlinkAPIGeneric._getAttachmentArgs() 44 | # needed in test_decoApiCallAddAttachment 45 | return {'filename': 'name %s' % attachmentfile, 46 | 'filetype': 'type %s' % attachmentfile, 47 | 'content' : 'content %s' % attachmentfile} 48 | 49 | @pytest.fixture() 50 | def dummy_api(): 51 | """ simulates testlink api client with required attributes devKey and 52 | _getAttachmentArgs 53 | """ 54 | return dummy_api_testlinkdecorator() 55 | 56 | def test_noWrapperName_decoApiCallWithoutArgs(): 57 | " decorator test: original function name should be unchanged " 58 | 59 | @decoApiCallWithoutArgs 60 | def orig_funcname1(a_api): 61 | "orig doc string1" 62 | return 'noArgs' 63 | 64 | assert 'orig_funcname1' == orig_funcname1.__name__ 65 | assert 'orig doc string1' == orig_funcname1.__doc__ 66 | assert 'testlinkdecorators_test' in orig_funcname1.__module__ 67 | 68 | def test_decoApiCallWithArgs(): 69 | " decorator test: positional and optional arguments should be registered " 70 | 71 | from testlink.testlinkargs import getMethodsWithPositionalArgs 72 | @decoMakerApiCallWithArgs(['Uno', 'due', 'tre'], ['quad']) 73 | def DummyMethod(a_api): 74 | "a dummy api method with 3 positional args and 1 optional arg" 75 | pass 76 | 77 | posArgs = getMethodsWithPositionalArgs() 78 | assert ['Uno', 'due', 'tre'] == posArgs['DummyMethod'] 79 | 80 | def test_noWrapperName_decoApiCallWithArgs(): 81 | " decorator test: original function name should be unchanged " 82 | 83 | @decoMakerApiCallWithArgs() 84 | def orig_funcname2(a_api): 85 | "orig doc string2" 86 | return 'noArgs' 87 | 88 | assert 'orig_funcname2' == orig_funcname2.__name__ 89 | assert 'orig doc string2' == orig_funcname2.__doc__ 90 | assert 'testlinkdecorators_test' in orig_funcname2.__module__ 91 | 92 | def test_decoApiCallAddDevKey(dummy_api): 93 | " decorator test: argsOptional should be extended with devKey" 94 | 95 | registerMethod('a_func') 96 | @decoApiCallAddDevKey 97 | def a_func(a_api, *argsPositional, **argsOptional): 98 | return argsPositional, argsOptional 99 | 100 | # check method argument definition 101 | allArgs = getArgsForMethod('a_func') 102 | assert (['devKey'], []) == allArgs 103 | # check call arguments 104 | response = a_func(dummy_api) 105 | assert {'devKey' : dummy_api.devKey} == response[1] 106 | 107 | def test_noWrapperName_decoApiCallAddDevKey(): 108 | " decorator test: original function name should be unchanged " 109 | 110 | registerMethod('orig_funcname3') 111 | @decoApiCallAddDevKey 112 | def orig_funcname3(a_api, *argsPositional, **argsOptional): 113 | "orig doc string3" 114 | return argsPositional, argsOptional 115 | 116 | assert 'orig_funcname3' == orig_funcname3.__name__ 117 | assert 'orig doc string3' == orig_funcname3.__doc__ 118 | assert 'testlinkdecorators_test' in orig_funcname3.__module__ 119 | 120 | def test_decoApiCallReplaceTLResponseError_NoCodeError(): 121 | " decorator test: TLResponseError (code=None) should be handled " 122 | 123 | @decoMakerApiCallReplaceTLResponseError() 124 | def a_func(a_api, *argsPositional, **argsOptional): 125 | raise TLResponseError('DummyMethod', 126 | argsOptional, 'Empty Response! ') 127 | 128 | response = a_func('dummy_api') 129 | assert [] == response 130 | 131 | def test_decoApiCallReplaceTLResponseError_CodeError(): 132 | " decorator test: TLResponseError (code=777) should be raised " 133 | 134 | @decoMakerApiCallReplaceTLResponseError() 135 | def a_func(a_api, *argsPositional, **argsOptional): 136 | raise TLResponseError('DummyMethod', 137 | argsOptional, 'Empty Response! ', 777) 138 | 139 | with pytest.raises(TLResponseError, match='777.*Empty'): 140 | a_func('dummy_api') 141 | 142 | def test_decoApiCallReplaceTLResponseError_CodeErrorOk(): 143 | " decorator test: TLResponseError (code=777) should be handled " 144 | 145 | @decoMakerApiCallReplaceTLResponseError(777) 146 | def a_func(a_api, *argsPositional, **argsOptional): 147 | raise TLResponseError('DummyMethod', 148 | argsOptional, 'Empty Response! ', 777) 149 | 150 | response = a_func('dummy_api') 151 | assert [] == response 152 | 153 | def test_decoApiCallReplaceTLResponseError_NoError(): 154 | " decorator test: response without TLResponseError should be passed " 155 | 156 | @decoMakerApiCallReplaceTLResponseError(777) 157 | def a_func(a_api, *argsPositional, **argsOptional): 158 | return argsOptional 159 | 160 | response = a_func('dummy_api', name='BigBird') 161 | assert {'name' : 'BigBird'} == response 162 | 163 | def test_decoApiCallReplaceTLResponseError_replaceValue(): 164 | " decorator test: TLResponseError should be replaced with {}" 165 | 166 | @decoMakerApiCallReplaceTLResponseError(replaceValue={}) 167 | def a_func(a_api, *argsPositional, **argsOptional): 168 | raise TLResponseError('DummyMethod', 169 | argsOptional, 'Empty Response! ') 170 | 171 | response = a_func('dummy_api') 172 | assert {} == response 173 | 174 | def test_noWrapperName_decoApiCallReplaceTLResponseError(): 175 | " decorator test: original function name should be unchanged " 176 | 177 | @decoMakerApiCallReplaceTLResponseError() 178 | def orig_funcname4(a_api, *argsPositional, **argsOptional): 179 | "orig doc string4" 180 | return argsPositional, argsOptional 181 | 182 | assert 'orig_funcname4' == orig_funcname4.__name__ 183 | assert 'orig doc string4' == orig_funcname4.__doc__ 184 | assert 'testlinkdecorators_test' in orig_funcname4.__module__ 185 | 186 | def test_decoApiCallAddAttachment(dummy_api): 187 | " decorator test: argsOptional should be extended attachment file infos" 188 | 189 | registerMethod('func_addAttachment') 190 | @decoApiCallAddAttachment 191 | def func_addAttachment(a_api, *argsPositional, **argsOptional): 192 | return argsPositional, argsOptional 193 | 194 | # check method argument definition 195 | allArgs = getArgsForMethod('func_addAttachment') 196 | assert (['devKey'], ['attachmentfile']) == allArgs 197 | # check call arguments 198 | response = func_addAttachment(dummy_api, 'a_file') 199 | assert response[1] == {'devKey' : dummy_api.devKey, 200 | 'filename': 'name a_file', 201 | 'filetype': 'type a_file', 202 | 'content' : 'content a_file'} 203 | 204 | def test_noWrapperName_decoApiCallAddAttachment(): 205 | " decorator test: original function name should be unchanged " 206 | 207 | registerMethod('orig_funcname5') 208 | @decoApiCallAddAttachment 209 | def orig_funcname5(a_api): 210 | "orig doc string5" 211 | return 'noArgs' 212 | 213 | assert 'orig_funcname5' == orig_funcname5.__name__ 214 | assert 'orig doc string5' == orig_funcname5.__doc__ 215 | assert 'testlinkdecorators_test' in orig_funcname5.__module__ 216 | 217 | def test_noWrapperName_decoApiCallChangePosToOptArg(): 218 | " decorator test: original function name should be unchanged " 219 | 220 | @decoMakerApiCallChangePosToOptArg(2, 'optArgName') 221 | def orig_funcname6(*argsPositional, **argsOptional): 222 | "orig doc string6" 223 | return argsPositional, argsOptional 224 | 225 | assert 'orig_funcname6' == orig_funcname6.__name__ 226 | assert 'orig doc string6' == orig_funcname6.__doc__ 227 | assert 'testlinkdecorators_test' in orig_funcname6.__module__ 228 | 229 | def test_decoApiCallChangePosToOptArg_posArg2(): 230 | " decorator test: change posArg 2" 231 | 232 | @decoMakerApiCallChangePosToOptArg(2, 'due') 233 | def a_func(a_api, *argsPositional, **argsOptional): 234 | return argsPositional, argsOptional 235 | 236 | #'Uno', 'due', 'tre', 'quad', 'cinque' 237 | # 2 posArgs 2optArgs -> 1posArg, 3optArg 238 | (posArgs, optArgs) = a_func('dummy_api', 1, 2, tre = 3, quad = 4 ) 239 | assert (1,) == posArgs 240 | assert {'due' : 2, 'tre' : 3, 'quad' : 4 } == optArgs 241 | 242 | # 3 posArgs 2optArgs -> 2posArg, 2optArg 243 | (posArgs, optArgs) = a_func('dummy_api', 1, 2, 3, quad = 4 , due = 5) 244 | assert (1,3) == posArgs 245 | assert {'due' : 2, 'quad' : 4 } == optArgs 246 | 247 | # 1 posArgs 2optArgs -> 1posArg, 2optArg 248 | (posArgs, optArgs) = a_func('dummy_api', 1, due = 2, tre = 3) 249 | assert (1,) == posArgs 250 | assert {'due' : 2, 'tre' : 3 } == optArgs 251 | 252 | # 0 posArgs 2optArgs -> 0posArg, 2optArg 253 | (posArgs, optArgs) = a_func('dummy_api', uno = 1, due = 2) 254 | assert () == posArgs 255 | assert {'uno' : 1, 'due' :2} == optArgs 256 | 257 | def test_decoApiCallChangePosToOptArg_posArg3(): 258 | " decorator test: change posArg 3" 259 | 260 | @decoMakerApiCallChangePosToOptArg(3, 'tre') 261 | def a_func(a_api, *argsPositional, **argsOptional): 262 | return argsPositional, argsOptional 263 | 264 | # 3 posArgs 0optArgs -> 2posArg, 1optArg 265 | (posArgs, optArgs) = a_func('dummy_api', 1, 2, 3 ) 266 | assert (1,2) == posArgs 267 | assert {'tre' : 3} == optArgs 268 | 269 | # 2 posArgs 0optArgs -> 2posArg, 0optArg 270 | (posArgs, optArgs) = a_func('dummy_api', 1, 2 ) 271 | assert (1,2) == posArgs 272 | assert {} == optArgs 273 | 274 | def test_decoApiCallChangePosToOptArg_posArgNeg1(): 275 | " decorator test: change posArg -1" 276 | 277 | @decoMakerApiCallChangePosToOptArg(-1, 'last') 278 | def a_func(a_api, *argsPositional, **argsOptional): 279 | return argsPositional, argsOptional 280 | 281 | # 3 posArgs 0optArgs -> 2posArg, 1optArg 282 | (posArgs, optArgs) = a_func('dummy_api', 1, 2, 3 ) 283 | assert (1,2,3) == posArgs 284 | assert {} == optArgs 285 | 286 | # 1 posArgs 0optArgs -> 0posArg, 1optArg 287 | (posArgs, optArgs) = a_func('dummy_api', 1 ) 288 | assert (1,) == posArgs 289 | assert {} == optArgs 290 | -------------------------------------------------------------------------------- /LICENSE-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /example/TestLinkExample_CF_KW.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | 21 | """ 22 | 23 | Shows how to use the TestLinkAPI for custom fields 24 | This example requires a special existing project with special custom fields 25 | assigned 26 | 27 | a) run example TestLinkExample.py 28 | - this creates a project like NEW_PROJECT_API-37 29 | b) load custom field definitions customFields_ExampleDefs.xml 30 | TL - Desktop - System - Define Custom Fields - Import 31 | c) assign custom fields to project NEW_PROJECT_API-37 32 | TL - Desktop - Test Project - Assign Custom Fields 33 | d) load keyword definitions keywords_ExampleDefs.xml 34 | TL - Desktop - Test Project - Keyword Management 35 | 36 | Script works with: 37 | 38 | TestProject NEW_PROJECT_API-37 39 | - TestSuite B - First Level 40 | - TestCase TESTCASE_B 41 | - TestPlan TestPlan_API A (Platform Small Bird) 42 | - Build TestlinkAPIClient v0.x.y 43 | 44 | Script creates custom values for TestCase TESTCASE_B 45 | - scope test specification and test execution 46 | 47 | Script returns custom field values from TestPlan and TestSuite, if the user has 48 | added manually some values. 49 | 50 | Cause of missing knowledge, how ids of kind 51 | - requirement and requirement specifications 52 | - testplan - testcase link 53 | could be requested via api, these example does not work currently. 54 | 55 | Script adds keywords KeyWord01 KeyWord02 KeyWord03 to test case TESTCASE_B, 56 | removes keyword KeyWord02 again. 57 | 58 | Script adds keywords KeyWord01 KeyWord02 to test case TESTCASE_AA, 59 | removes keyword KeyWord01 again. 60 | 61 | """ 62 | from testlink import TestlinkAPIClient, TestLinkHelper 63 | from testlink.testlinkerrors import TLResponseError 64 | import sys, os.path 65 | from platform import python_version 66 | 67 | # precondition a) 68 | # SERVER_URL and KEY are defined in environment 69 | # TESTLINK_API_PYTHON_SERVER_URL=http://YOURSERVER/testlink/lib/api/xmlrpc.php 70 | # TESTLINK_API_PYTHON_DEVKEY=7ec252ab966ce88fd92c25d08635672b 71 | # 72 | # alternative precondition b) 73 | # SERVEUR_URL and KEY are defined as command line arguments 74 | # python TestLinkExample.py --server_url http://YOURSERVER/testlink/lib/api/xmlrpc.php 75 | # --devKey 7ec252ab966ce88fd92c25d08635672b 76 | # 77 | # ATTENTION: With TestLink 1.9.7, cause of the new REST API, the SERVER_URL 78 | # has changed from 79 | # (old) http://YOURSERVER/testlink/lib/api/xmlrpc.php 80 | # to 81 | # (new) http://YOURSERVER/testlink/lib/api/xmlrpc/v1/xmlrpc.php 82 | tl_helper = TestLinkHelper() 83 | tl_helper.setParamsFromArgs('''Shows how to use the TestLinkAPI for CustomFields. 84 | => requires an existing project NEW_PROJECT_API-*''') 85 | myTestLink = tl_helper.connect(TestlinkAPIClient) 86 | 87 | myPyVersion = python_version() 88 | myPyVersionShort = myPyVersion.replace('.', '')[:2] 89 | 90 | NEWTESTPLAN_A="TestPlan_API A" 91 | # NEWTESTPLAN_B="TestPlan_API B" 92 | # NEWTESTPLAN_C="TestPlan_API C - DeleteTest" 93 | # NEWPLATFORM_A='Big Birds %s' % myPyVersionShort 94 | NEWPLATFORM_B='Small Birds' 95 | # NEWPLATFORM_C='Ugly Birds' 96 | NEWTESTSUITE_A="A - First Level" 97 | NEWTESTSUITE_B="B - First Level" 98 | NEWTESTSUITE_AA="AA - Second Level" 99 | NEWTESTCASE_AA="TESTCASE_AA" 100 | NEWTESTCASE_B="TESTCASE_B" 101 | # myApiVersion='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__) 102 | # NEWBUILD_A='%s' % myApiVersion 103 | # NEWBUILD_B='%s' % myApiVersion 104 | # NEWBUILD_C='%s - DeleteTest' % myApiVersion 105 | # NEWBUILD_D='%s - copyTestersTest' % myApiVersion 106 | 107 | this_file_dirname=os.path.dirname(__file__) 108 | NEWATTACHMENT_PY= os.path.join(this_file_dirname, 'TestLinkExample.py') 109 | NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png') 110 | 111 | # Servers TestLink Version 112 | myTLVersion = myTestLink.testLinkVersion() 113 | myTLVersionShort = myTLVersion.replace('.', '') 114 | 115 | NEWPROJECT="NEW_PROJECT_API-%s" % myPyVersionShort 116 | NEWPREFIX="NPROAPI%s" % myPyVersionShort 117 | ITSNAME="myITS" 118 | 119 | # used connection settings 120 | print( myTestLink.connectionInfo() ) 121 | print( "" ) 122 | 123 | # get information - TestProject 124 | newProject = myTestLink.getTestProjectByName(NEWPROJECT) 125 | print( "getTestProjectByName", newProject ) 126 | newProjectID = newProject['id'] 127 | print( "Project '%s' - id: %s" % (NEWPROJECT,newProjectID) ) 128 | response = myTestLink.getProjectKeywords(newProjectID) 129 | print("getProjectKeywords", response) 130 | 131 | # get information - TestPlan 132 | newTestPlan = myTestLink.getTestPlanByName(NEWPROJECT, NEWTESTPLAN_A) 133 | print( "getTestPlanByName", newTestPlan ) 134 | newTestPlanID_A = newTestPlan[0]['id'] 135 | print( "Test Plan '%s' - id: %s" % (NEWTESTPLAN_A,newTestPlanID_A) ) 136 | response = myTestLink.getTotalsForTestPlan(newTestPlanID_A) 137 | print( "getTotalsForTestPlan", response ) 138 | response = myTestLink.getBuildsForTestPlan(newTestPlanID_A) 139 | print( "getBuildsForTestPlan", response ) 140 | newBuildID_A = response[0]['id'] 141 | newBuildName_A = response[0]['name'] 142 | # get information - TestSuite 143 | response = myTestLink.getTestSuitesForTestPlan(newTestPlanID_A) 144 | print( "getTestSuitesForTestPlan", response ) 145 | newTestSuiteID_A=response[0]['id'] 146 | newTestSuiteID_AA=response[1]['id'] 147 | newTestSuiteID_B=response[2]['id'] 148 | newTestSuite = myTestLink.getTestSuiteByID(newTestSuiteID_B) 149 | print( "getTestSuiteByID", newTestSuite ) 150 | # get informationen - TestCase_B 151 | response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, testprojectname=NEWPROJECT) 152 | print( "getTestCaseIDByName", response ) 153 | newTestCaseID_B = response[0]['id'] 154 | tc_b_full_ext_id = myTestLink.getTestCase(newTestCaseID_B)[0]['full_tc_external_id'] 155 | print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_B, newTestCaseID_B, tc_b_full_ext_id) ) 156 | # get informationen - TestCase_AA 157 | response = myTestLink.getTestCaseIDByName(NEWTESTCASE_AA, testprojectname=NEWPROJECT) 158 | print( "getTestCaseIDByName", response ) 159 | newTestCaseID_AA = response[0]['id'] 160 | tc_aa_full_ext_id = myTestLink.getTestCase(newTestCaseID_AA)[0]['full_tc_external_id'] 161 | print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_AA, newTestCaseID_AA, tc_aa_full_ext_id) ) 162 | 163 | 164 | # add keywords to TestCase B and TestCase AA 165 | response = myTestLink.addTestCaseKeywords( 166 | {tc_b_full_ext_id : ['KeyWord01', 'KeyWord03', 'KeyWord02'], 167 | tc_aa_full_ext_id : ['KeyWord01', 'KeyWord02', 'KeyWord03']}) 168 | print( "addTestCaseKeywords", response ) 169 | # remove keywords from TestCase B and TestCase AA 170 | response = myTestLink.removeTestCaseKeywords( 171 | {tc_b_full_ext_id : ['KeyWord02'], 172 | tc_aa_full_ext_id : ['KeyWord01', 'KeyWord03']}) 173 | print( "removeTestCaseKeywords", response ) 174 | 175 | 176 | # list test cases with assigned keywords B 177 | response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, True, 178 | 'full', getkeywords=True) 179 | print( "getTestCasesForTestSuite B (deep=True)", response ) 180 | response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, False, 181 | 'full', getkeywords=True) 182 | print( "getTestCasesForTestSuite B (deep=False)", response ) 183 | 184 | # get informationen - TestCase_B again 185 | newTestCase_B = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0] 186 | print( "getTestCase B", newTestCase_B ) 187 | 188 | # return keyword list for TestCase_B 189 | response = myTestLink.listKeywordsForTC(newTestCaseID_B) 190 | print( "listKeywordsForTC B", response ) 191 | # return keyword lists for all test cases of test newTestSuite_B 192 | response = myTestLink.listKeywordsForTS(newTestSuiteID_B) 193 | print( "listKeywordsForTS B", response ) 194 | 195 | # list test cases with assigned keywords AA 196 | response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, True, 197 | 'full', getkeywords=True) 198 | print( "getTestCasesForTestSuite A (deep=True)", response ) 199 | response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, False, 200 | 'full', getkeywords=True) 201 | print( "getTestCasesForTestSuite A (deep=False)", response ) 202 | 203 | # get informationen - TestCase_AA again 204 | newTestCase_AA = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0] 205 | print( "getTestCase AA", newTestCase_AA ) 206 | 207 | # return keyword list for TestCase_AA 208 | response = myTestLink.listKeywordsForTC(newTestCaseID_AA) 209 | print( "listKeywordsForTC AA", response ) 210 | # return keyword lists for all test cases of test newTestSuite_A 211 | response = myTestLink.listKeywordsForTS(newTestSuiteID_AA) 212 | print( "listKeywordsForTS AA", response ) 213 | 214 | 215 | response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_B) 216 | print("getTestCaseKeywords B", response) 217 | response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_AA) 218 | print("getTestCaseKeywords AA", response) 219 | 220 | # new execution result with custom field data 221 | # TC_B passed, explicit build and some notes , TC identified with internal id 222 | newResult = myTestLink.reportTCResult(newTestCaseID_B, newTestPlanID_A, 223 | newBuildName_A, 'p', "bugid 4711 is assigned", 224 | platformname=NEWPLATFORM_B, bugid='4711', 225 | customfields={'cf_tc_ex_string' : 'a custom exec value set via api', 226 | 'cf_tc_sd_listen' : 'ernie'}) 227 | print( "reportTCResult", newResult ) 228 | 229 | # get execution results 230 | lastResult = myTestLink.getLastExecutionResult(newTestPlanID_A, newTestCaseID_B, 231 | options={'getBugs' : True})[0] 232 | print( "getLastExecutionResult", lastResult ) 233 | 234 | # map of used ids 235 | args = {'devKey' : myTestLink.devKey, 236 | 'testprojectid' : newProjectID, 237 | 'testcaseexternalid' : newTestCase_B['full_tc_external_id'], 238 | 'version' : int(newTestCase_B['version']), 239 | 'tcversion_number' : lastResult['tcversion_number'], 240 | 'executionid' : lastResult['id'], 241 | 'linkid' : 779, 242 | 'testsuiteid': newTestSuiteID_B, 243 | 'testplanid': lastResult['testplan_id'], 244 | 'reqspecid': 7789, 245 | 'requirementid': 7791, 246 | 'buildid':newBuildID_A} 247 | 248 | # get CustomField Value - TestCase Execution 249 | response = myTestLink.getTestCaseCustomFieldExecutionValue( 250 | 'cf_tc_ex_string', args['testprojectid'], args['tcversion_number'], 251 | args['executionid'] , args['testplanid'] ) 252 | print( "getTestCaseCustomFieldExecutionValue", response ) 253 | 254 | # update CustomField Value - TestCase SpecDesign 255 | response = myTestLink.updateTestCaseCustomFieldDesignValue( 256 | args['testcaseexternalid'], args['version'], 257 | args['testprojectid'], 258 | {'cf_tc_sd_string' : 'A custom SpecDesign value set via api', 259 | 'cf_tc_sd_list' : 'bibo'}) 260 | print( "updateTestCaseCustomFieldDesignValue", response ) 261 | 262 | # get CustomField Value - TestCase SpecDesign 263 | #response = myTestLink._callServer('getTestCaseCustomFieldDesignValue', args) 264 | response = myTestLink.getTestCaseCustomFieldDesignValue( 265 | args['testcaseexternalid'], args['version'], 266 | args['testprojectid'], 'cf_tc_sd_string', 'full') 267 | print( "getTestCaseCustomFieldDesignValue full", response ) 268 | 269 | response = myTestLink.getTestCaseCustomFieldDesignValue( 270 | args['testcaseexternalid'], args['version'], 271 | args['testprojectid'], 'cf_tc_sd_string', 'value') 272 | print( "getTestCaseCustomFieldDesignValue value", response ) 273 | 274 | response = myTestLink.getTestCaseCustomFieldDesignValue( 275 | args['testcaseexternalid'], args['version'], 276 | args['testprojectid'], 'cf_tc_sd_list', 'simple') 277 | print( "getTestCaseCustomFieldDesignValue simple", response ) 278 | 279 | # get CustomField Value - TestCase Testplan Design 280 | response = myTestLink.getTestCaseCustomFieldTestPlanDesignValue( 281 | 'cf_tc_pd_string', args['testprojectid'], args['tcversion_number'], 282 | args['testplanid'], args['linkid']) 283 | print( "getTestCaseCustomFieldTestPlanDesignValue", response ) 284 | 285 | # get CustomFields Values from all test cases of a TestPlan 286 | response = myTestLink.getTestCasesForTestPlan(args['testplanid'], 287 | customfields=True) 288 | print("getTestCasesForTestPlan with all customfields", response) 289 | response = myTestLink.getTestCasesForTestPlan(args['testplanid'], 290 | customfields=['cf_tc_sd_string']) 291 | print("getTestCasesForTestPlan with specific customfields", response) 292 | 293 | # update CustomField Value - TestSuite SpecDesign 294 | response = myTestLink.updateTestSuiteCustomFieldDesignValue( 295 | args['testprojectid'], args['testsuiteid'], 296 | {'cf_ts_string' : 'A custom TestSuite value set via api'}) 297 | print( "updateTestSuiteCustomFieldDesignValue", response ) 298 | 299 | # get CustomField Value - TestSuite 300 | response = myTestLink.getTestSuiteCustomFieldDesignValue( 301 | 'cf_ts_string', args['testprojectid'], args['testsuiteid']) 302 | print( "getTestSuiteCustomFieldDesignValue", response ) 303 | 304 | # get CustomField Value - TestPlan 305 | response = myTestLink.getTestPlanCustomFieldDesignValue( 306 | 'cf_tp_string', args['testprojectid'], args['testplanid']) 307 | print( "getTestPlanCustomFieldDesignValue", response ) 308 | 309 | # get CustomField Value - Requirement Specification 310 | response = myTestLink.getReqSpecCustomFieldDesignValue( 311 | 'cf_req_sd_string', args['testprojectid'], args['reqspecid']) 312 | print( "getReqSpecCustomFieldDesignValue", response ) 313 | 314 | 315 | # get CustomField Value - Requirement Specification 316 | response = myTestLink.getRequirementCustomFieldDesignValue( 317 | 'cf_req_string',args['testprojectid'], args['requirementid']) 318 | print( "getRequirementCustomFieldDesignValue", response ) 319 | 320 | # update CustomField Value - Build 321 | response = myTestLink.updateBuildCustomFieldsValues( 322 | args['testprojectid'], args['testplanid'], args['buildid'], 323 | {'cf_b_string' : 'A custom Build value set via api'}) 324 | print( "updateBuildCustomFieldsValues", response ) 325 | 326 | 327 | -------------------------------------------------------------------------------- /example/TestLinkExampleGenericApi_Req.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # Copyright 2017-2019 Luiko Czub, TestLink-API-Python-client developers 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # ------------------------------------------------------------------------ 19 | 20 | 21 | """ 22 | 23 | Shows how to use the TestLinkAPIGeneric for requirements 24 | This example requires a special existing project with special custom fields 25 | assigned and a set of requirements 26 | 27 | a) run example TestLinkExampleGenericApi.py 28 | - this creates a project like PROJECT_API_GENERIC-36 29 | b) load custom field definitions customFields_ExampleDefs.xml 30 | TL - Desktop - System - Define Custom Fields - Import 31 | c) assign custom fields to project PROJECT_API_GENERIC-36 32 | TL - Desktop - Test Project - Assign Custom Fields 33 | d) load requirement definitions all-req.xml to project PROJECT_API_GENERIC-36 34 | TL - Desktop - Requirements - Requirement Specification 35 | 36 | Script works with: 37 | 38 | TestProject PROJECT_API_GENERIC-36 39 | - TestSuite B - First Level 40 | - TestCase TESTCASE_B 41 | - TestPlan TestPlan_API_GENERIC A (Platform Small Bird) 42 | - Build TestlinkAPIGeneric v0.x.y 43 | 44 | Script creates custom values for TestCase TESTCASE_B 45 | - scope test specification and test execution 46 | 47 | Script returns custom field values from TestPlan and TestSuite, if the user has 48 | added manually some values. 49 | 50 | Cause of missing knowledge, how ids of kind 51 | - requirement and requirement specifications 52 | - testplan - testcase link 53 | could be requested via api, these example does not work currently. 54 | 55 | Script adds keywords KeyWord01 KeyWord02 KeyWord03 to test case TESTCASE_B, 56 | removes keyword KeyWord02 again. 57 | 58 | Script adds keywords KeyWord01 KeyWord02 to test case TESTCASE_AA, 59 | removes keyword KeyWord01 again. 60 | 61 | """ 62 | from testlink import TestlinkAPIGeneric, TestLinkHelper 63 | from testlink.testlinkerrors import TLResponseError 64 | import sys, os.path 65 | from platform import python_version 66 | 67 | # precondition a) 68 | # SERVER_URL and KEY are defined in environment 69 | # TESTLINK_API_PYTHON_SERVER_URL=http://YOURSERVER/testlink/lib/api/xmlrpc.php 70 | # TESTLINK_API_PYTHON_DEVKEY=7ec252ab966ce88fd92c25d08635672b 71 | # 72 | # alternative precondition b) 73 | # SERVEUR_URL and KEY are defined as command line arguments 74 | # python TestLinkExample.py --server_url http://YOURSERVER/testlink/lib/api/xmlrpc.php 75 | # --devKey 7ec252ab966ce88fd92c25d08635672b 76 | # 77 | # ATTENTION: With TestLink 1.9.7, cause of the new REST API, the SERVER_URL 78 | # has changed from 79 | # (old) http://YOURSERVER/testlink/lib/api/xmlrpc.php 80 | # to 81 | # (new) http://YOURSERVER/testlink/lib/api/xmlrpc/v1/xmlrpc.php 82 | tl_helper = TestLinkHelper() 83 | tl_helper.setParamsFromArgs('''Shows how to use the TestLinkAPI for CustomFields. 84 | => requires an existing project PROJECT_API_GENERIC-*''') 85 | myTestLink = tl_helper.connect(TestlinkAPIGeneric) 86 | 87 | myPyVersion = python_version() 88 | myPyVersionShort = myPyVersion.replace('.', '')[:2] 89 | 90 | NEWTESTPLAN_A="TestPlan_API_GENERIC A" 91 | # NEWTESTPLAN_B="TestPlan_API_GENERIC B" 92 | # NEWTESTPLAN_C="TestPlan_API_GENERIC C - DeleteTest" 93 | # NEWPLATFORM_A='Big Bird %s' % myPyVersionShort 94 | NEWPLATFORM_B='Small Bird' 95 | # NEWPLATFORM_C='Ugly Bird' 96 | NEWTESTSUITE_A="A - First Level" 97 | NEWTESTSUITE_B="B - First Level" 98 | NEWTESTSUITE_AA="AA - Second Level" 99 | NEWTESTCASE_AA="TESTCASE_AA" 100 | NEWTESTCASE_B="TESTCASE_B" 101 | # myApiVersion='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__) 102 | # NEWBUILD_A='%s' % myApiVersion 103 | # NEWBUILD_B='%s' % myApiVersion 104 | # NEWBUILD_C='%s - DeleteTest' % myApiVersion 105 | # NEWBUILD_D='%s - copyTestersTest' % myApiVersion 106 | 107 | this_file_dirname=os.path.dirname(__file__) 108 | NEWATTACHMENT_PY= os.path.join(this_file_dirname, 'TestLinkExampleGenericApi.py') 109 | NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png') 110 | 111 | # Servers TestLink Version 112 | myTLVersion = myTestLink.testLinkVersion() 113 | myTLVersionShort = myTLVersion.replace('.', '') 114 | 115 | NEWPROJECT="PROJECT_API_GENERIC-%s" % myPyVersionShort 116 | NEWPREFIX="GPROAPI%s" % myPyVersionShort 117 | ITSNAME="myITS" 118 | 119 | # used connection settings 120 | print( myTestLink.connectionInfo() ) 121 | print( "" ) 122 | 123 | # get information - TestProject 124 | newProject = myTestLink.getTestProjectByName(NEWPROJECT) 125 | print( "getTestProjectByName", newProject ) 126 | newProjectID = newProject['id'] 127 | print( "Project '%s' - id: %s" % (NEWPROJECT,newProjectID) ) 128 | response = myTestLink.getProjectKeywords(newProjectID) 129 | print("getProjectKeywords", response) 130 | 131 | # get information - TestPlan 132 | newTestPlan = myTestLink.getTestPlanByName(NEWPROJECT, NEWTESTPLAN_A) 133 | print( "getTestPlanByName", newTestPlan ) 134 | newTestPlanID_A = newTestPlan[0]['id'] 135 | print( "Test Plan '%s' - id: %s" % (NEWTESTPLAN_A,newTestPlanID_A) ) 136 | response = myTestLink.getTotalsForTestPlan(newTestPlanID_A) 137 | print( "getTotalsForTestPlan", response ) 138 | response = myTestLink.getBuildsForTestPlan(newTestPlanID_A) 139 | print( "getBuildsForTestPlan", response ) 140 | newBuildID_A = response[0]['id'] 141 | newBuildName_A = response[0]['name'] 142 | # get information - TestSuite 143 | response = myTestLink.getTestSuitesForTestPlan(newTestPlanID_A) 144 | print( "getTestSuitesForTestPlan", response ) 145 | newTestSuiteID_A=response[0]['id'] 146 | newTestSuiteID_AA=response[1]['id'] 147 | newTestSuiteID_B=response[2]['id'] 148 | newTestSuite = myTestLink.getTestSuiteByID(newTestSuiteID_B) 149 | print( "getTestSuiteByID", newTestSuite ) 150 | # get informationen - TestCase_B 151 | response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, testprojectname=NEWPROJECT) 152 | print( "getTestCaseIDByName", response ) 153 | newTestCaseID_B = response[0]['id'] 154 | tc_b_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0]['full_tc_external_id'] 155 | print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_B, newTestCaseID_B, tc_b_full_ext_id) ) 156 | # get informationen - TestCase_AA 157 | response = myTestLink.getTestCaseIDByName(NEWTESTCASE_AA, testprojectname=NEWPROJECT) 158 | print( "getTestCaseIDByName", response ) 159 | newTestCaseID_AA = response[0]['id'] 160 | tc_aa_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0]['full_tc_external_id'] 161 | print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_AA, newTestCaseID_AA, tc_aa_full_ext_id) ) 162 | 163 | 164 | # add keywords to TestCase B and TestCase AA 165 | response = myTestLink.addTestCaseKeywords( 166 | {tc_b_full_ext_id : ['KeyWord01', 'KeyWord03', 'KeyWord02'], 167 | tc_aa_full_ext_id : ['KeyWord01', 'KeyWord02', 'KeyWord03']}) 168 | print( "addTestCaseKeywords", response ) 169 | # remove keywords from TestCase B and TestCase AA 170 | response = myTestLink.removeTestCaseKeywords( 171 | {tc_b_full_ext_id : ['KeyWord02'], 172 | tc_aa_full_ext_id : ['KeyWord01', 'KeyWord03']}) 173 | print( "removeTestCaseKeywords", response ) 174 | 175 | 176 | # list test cases with assigned keywords B 177 | response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, deep=True, 178 | details='full', getkeywords=True) 179 | print( "getTestCasesForTestSuite B (deep=True)", response ) 180 | response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, deep=False, 181 | details='full', getkeywords=True) 182 | print( "getTestCasesForTestSuite B (deep=False)", response ) 183 | 184 | # get informationen - TestCase_B again 185 | newTestCase_B = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0] 186 | print( "getTestCase B", newTestCase_B ) 187 | 188 | # # return keyword list for TestCase_B - service method TestLinkAPIClient 189 | # response = myTestLink.listKeywordsForTC(newTestCaseID_B) 190 | # print( "listKeywordsForTC B", response ) 191 | # # return keyword lists for all test cases of test newTestSuite_B - service method TestLinkAPIClient 192 | # response = myTestLink.listKeywordsForTS(newTestSuiteID_B) 193 | # print( "listKeywordsForTS B", response ) 194 | 195 | # list test cases with assigned keywords AA 196 | response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, deep=True, 197 | details='full', getkeywords=True) 198 | print( "getTestCasesForTestSuite A (deep=True)", response ) 199 | response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, deep=False, 200 | details='full', getkeywords=True) 201 | print( "getTestCasesForTestSuite A (deep=False)", response ) 202 | 203 | # get informationen - TestCase_AA again 204 | newTestCase_AA = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0] 205 | print( "getTestCase AA", newTestCase_AA ) 206 | 207 | # # return keyword list for TestCase_AA - service method TestLinkAPIClient 208 | # response = myTestLink.listKeywordsForTC(newTestCaseID_AA) 209 | # print( "listKeywordsForTC AA", response ) - service method TestLinkAPIClient 210 | # # return keyword lists for all test cases of test newTestSuite_A 211 | # response = myTestLink.listKeywordsForTS(newTestSuiteID_AA) 212 | # print( "listKeywordsForTS AA", response ) 213 | 214 | 215 | response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_B) 216 | print("getTestCaseKeywords B", response) 217 | response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_AA) 218 | print("getTestCaseKeywords AA", response) 219 | 220 | # new execution result with custom field data 221 | # TC_B passed, explicit build and some notes , TC identified with internal id 222 | newResult = myTestLink.reportTCResult(newTestPlanID_A, 'p', 223 | buildname=newBuildName_A, testcaseid=newTestCaseID_B, 224 | platformname=NEWPLATFORM_B, notes="bugid 4711 is assigned", 225 | bugid='4711', 226 | customfields={'cf_tc_ex_string' : 'a custom exec value set via api', 227 | 'cf_tc_sd_listen' : 'ernie'}) 228 | print( "reportTCResult", newResult ) 229 | 230 | # get execution results 231 | lastResult = myTestLink.getLastExecutionResult(newTestPlanID_A, 232 | testcaseid=newTestCaseID_B, 233 | options={'getBugs' : True})[0] 234 | print( "getLastExecutionResult", lastResult ) 235 | 236 | # get all requirement for the testprojekt 237 | req_list = myTestLink.getRequirements(newProjectID) 238 | print ( "getRequirements all", req_list ) 239 | reqA = req_list[0] 240 | reqB = req_list[1] 241 | 242 | # add requirement reqA to testcase B 243 | # response = myTestLink.assignRequirements(newTestCase_B['full_tc_external_id'], newProjectID, 244 | # [{'req_spec' : reqA['srs_id'], 'requirements' : [ reqA['id'] ]} ] 245 | # ) 246 | print("assignRequirements reqA to TC-B", 247 | "sorry not possible - required srs_id -rec_spec(id) not available") 248 | 249 | # get coverage for requirements reqA 250 | response = myTestLink.getReqCoverage(newProjectID, reqA['req_doc_id']) 251 | print("getReqCoverage reqA", response) 252 | 253 | # add png file as Attachemnt to a requirement specification. 254 | print("uploadRequirementSpecificationAttachment", 255 | "sorry not possible - required srs_id -rec_spec(id) not available") 256 | # add png file as Attachemnt to a requirement. 257 | #a_file=open(NEWATTACHMENT_PNG, mode='rb') 258 | newAttachment = myTestLink.uploadRequirementAttachment(NEWATTACHMENT_PNG, reqA['id'], 259 | title='PNG Example', description='PNG Attachment Example for a requirement') 260 | print("uploadRequirementAttachment", newAttachment) 261 | 262 | 263 | # map of used ids 264 | args = {'devKey' : myTestLink.devKey, 265 | 'testprojectid' : newProjectID, 266 | 'testcaseexternalid' : newTestCase_B['full_tc_external_id'], 267 | 'version' : int(newTestCase_B['version']), 268 | 'tcversion_number' : lastResult['tcversion_number'], 269 | 'executionid' : lastResult['id'], 270 | 'linkid' : 779, 271 | 'testsuiteid': newTestSuiteID_B, 272 | 'testplanid': lastResult['testplan_id'], 273 | 'reqspecid': 7789, 274 | # 'reqspecid': reqA['srs_id'], 275 | 'requirementid': reqA['id'], 276 | 'buildid':newBuildID_A} 277 | 278 | # get CustomField Value - TestCase Execution 279 | response = myTestLink.getTestCaseCustomFieldExecutionValue( 280 | 'cf_tc_ex_string', args['testprojectid'], args['tcversion_number'], 281 | args['executionid'] , args['testplanid'] ) 282 | print( "getTestCaseCustomFieldExecutionValue", response ) 283 | 284 | # update CustomField Value - TestCase SpecDesign 285 | response = myTestLink.updateTestCaseCustomFieldDesignValue( 286 | args['testcaseexternalid'], args['version'], 287 | args['testprojectid'], 288 | {'cf_tc_sd_string' : 'A custom SpecDesign value set via api', 289 | 'cf_tc_sd_list' : 'bibo'}) 290 | print( "updateTestCaseCustomFieldDesignValue", response ) 291 | 292 | # get CustomField Value - TestCase SpecDesign 293 | #response = myTestLink._callServer('getTestCaseCustomFieldDesignValue', args) 294 | response = myTestLink.getTestCaseCustomFieldDesignValue( 295 | args['testcaseexternalid'], args['version'], 296 | args['testprojectid'], 'cf_tc_sd_string', details='full') 297 | print( "getTestCaseCustomFieldDesignValue full", response ) 298 | 299 | response = myTestLink.getTestCaseCustomFieldDesignValue( 300 | args['testcaseexternalid'], args['version'], 301 | args['testprojectid'], 'cf_tc_sd_string', details='value') 302 | print( "getTestCaseCustomFieldDesignValue value", response ) 303 | 304 | response = myTestLink.getTestCaseCustomFieldDesignValue( 305 | args['testcaseexternalid'], args['version'], 306 | args['testprojectid'], 'cf_tc_sd_list', details='simple') 307 | print( "getTestCaseCustomFieldDesignValue simple", response ) 308 | 309 | # get CustomField Value - TestCase Testplan Design 310 | response = myTestLink.getTestCaseCustomFieldTestPlanDesignValue( 311 | 'cf_tc_pd_string', args['testprojectid'], args['tcversion_number'], 312 | args['testplanid'], args['linkid']) 313 | print( "getTestCaseCustomFieldTestPlanDesignValue", response ) 314 | 315 | # update CustomField Value - TestSuite SpecDesign 316 | response = myTestLink.updateTestSuiteCustomFieldDesignValue( 317 | args['testprojectid'], args['testsuiteid'], 318 | {'cf_ts_string' : 'A custom TestSuite value set via api'}) 319 | print( "updateTestSuiteCustomFieldDesignValue", response ) 320 | 321 | # get CustomField Value - TestSuite 322 | response = myTestLink.getTestSuiteCustomFieldDesignValue( 323 | 'cf_ts_string', args['testprojectid'], args['testsuiteid']) 324 | print( "getTestSuiteCustomFieldDesignValue", response ) 325 | 326 | # get CustomField Value - TestPlan 327 | response = myTestLink.getTestPlanCustomFieldDesignValue( 328 | 'cf_tp_string', args['testprojectid'], args['testplanid']) 329 | print( "getTestPlanCustomFieldDesignValue", response ) 330 | 331 | # get CustomField Value - Requirement Specification 332 | response = myTestLink.getReqSpecCustomFieldDesignValue( 333 | 'cf_req_sd_string', args['testprojectid'], args['reqspecid']) 334 | print( "getReqSpecCustomFieldDesignValue", response ) 335 | 336 | 337 | # get CustomField Value - Requirement Specification 338 | response = myTestLink.getRequirementCustomFieldDesignValue( 339 | 'cf_req_string',args['testprojectid'], args['requirementid']) 340 | print( "getRequirementCustomFieldDesignValue", response ) 341 | 342 | # update CustomField Value - Build 343 | response = myTestLink.updateBuildCustomFieldsValues( 344 | args['testprojectid'], args['testplanid'], args['buildid'], 345 | {'cf_b_string' : 'A custom Build value set via api'}) 346 | print( "updateBuildCustomFieldsValues", response ) 347 | 348 | 349 | --------------------------------------------------------------------------------