├── 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 |
17 | 1
18 | 1
19 | 1
20 | a use case]]>
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
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 |
57 | 1
58 | 1
59 | 1
60 | a restriction]]>
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
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 |
97 | 1
98 | 1
99 | 2
100 | a system function]]>
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
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=',
110 | 'filename=', 'filetype=',
111 | 'content=']),
112 | ('createPlatform',[',', ',', 'notes=',
113 | 'platformondesign=',
114 | 'platformonexecution=']),
115 | ('closeBuild', ['']),
116 | ('createUser', ['', '', '', '',
117 | 'password=']),
118 | ('setUserRoleOnProject', ['', '', ''])
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', [',']),
130 | ('createBuild',['buildnotes=']),
131 | ('getTestCaseAttachments',['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=']),
142 | ('createBuild',[',']),
143 | ('getTestCaseAttachments',[','])
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(, , [note=], [active=],
30 | [public=], [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 | \n\ntl.getUserByLogin\n...\n\n"
215 | reply: 'HTTP/1.1 200 OK\r\n'
216 | header: Date header: Server header: ... body: b'\n\n ...'
217 | body: b'1\n\n \n ...'
218 | body: b'... \n\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(, , [note=], [active=],
30 | [public=], [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 | \n\ntl.getUserByLogin\n...\n\n"
224 | reply: 'HTTP/1.1 200 OK\r\n'
225 | header: Date header: Server header: ... body: b'\n\n ...'
226 | body: b'1\n\n \n ...'
227 | body: b'... \n\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 |
--------------------------------------------------------------------------------