diff --git a/.env b/.env
new file mode 100644
index 0000000..f465cdd
--- /dev/null
+++ b/.env
@@ -0,0 +1,3 @@
+PYTHONPATH="./src"
+TESTLINK_API_PYTHON_SERVER_URL="http://localhost:8085/lib/api/xmlrpc/v1/xmlrpc.php"
+TESTLINK_API_PYTHON_DEVKEY="48072c25257af9f477a22c97a3858337"
diff --git a/.gitignore b/.gitignore
index 4caf723..e0ff092 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,10 @@
+.tox
+.idea
.project
.pydevproject
.settings/
build/
*.pyc
-dist/
+dist/
MANIFEST
+.pytest_cache/
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..0bac8a5
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,11 @@
+{
+ "python.autoComplete.extraPaths": [
+ "${workspaceRoot}/src"
+ ],
+ "python.testing.pytestArgs": [
+ "test"
+ ],
+ "python.testing.unittestEnabled": false,
+ "python.testing.pytestEnabled": true
+
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..0fafc44
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,41 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
+ // for the documentation about the tasks.json format
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "echo",
+ "type": "shell",
+ "command": "echo Hello"
+ },
+ {
+ "label": "test Testlink Api sample",
+ "type": "process",
+ "detail": "Run sample Testlink Api Python Client communication",
+ "command": "${config:python.defaultInterpreterPath}",
+ "args": [ "${workspaceFolder}/example/${input:apiSample}.py" ],
+ "group": "test",
+ "presentation": {
+ "clear": true,
+ "panel": "dedicated"
+ },
+ "problemMatcher": ["$python"],
+ "options": {
+ "cwd": "${workspaceFolder}",
+ "env": { "PYTHONPATH" : "./src",
+ "TESTLINK_API_PYTHON_SERVER_URL" : "http://localhost:8085/lib/api/xmlrpc/v1/xmlrpc.php",
+ "TESTLINK_API_PYTHON_DEVKEY" : "48072c25257af9f477a22c97a3858337"
+ }
+ }
+ }
+ ],
+ "inputs": [
+ {
+ "type": "pickString",
+ "id": "apiSample",
+ "description": "Which TL API sample to run ?",
+ "options": ["TestLinkExample", "TestLinkExampleGenericApi","TestLinkExample_CF_KW","TestLinkExampleGenericApi_Req"],
+ "default": "TestLinkExample"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/CHANGES.rst b/CHANGES.rst
new file mode 100644
index 0000000..c6453ef
--- /dev/null
+++ b/CHANGES.rst
@@ -0,0 +1,719 @@
+Changes in TestLink-API-Python-client Source Distribution
+=========================================================
+
+TestLink-API-Python-client v0.8.2 (under develop)
+------------------------------------------------
+support for TL 1.9.20_fixed changes and py39
+
+main topic is to support TL 1.9.20_fixed api changes
+
+implement 1.9.20_fixed new api interfaces - #141
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+new TestlinkAPIGeneric and TestlinkAPIClient api methods
+
+- createUser(, , , , [password=])
+- setUserRoleOnProject(, , )
+
+new TestlinkAPIClient service methods
+
+- ensureUserExist(, [firstname=], [lastname=],
+ [email=, [password=])
+- ensureUserExistWithProjectRole(, , ,
+ [firstname=], [lastname=], [email=, [password=])
+
+implement 1.9.20_fixed changed api interfaces - #139
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+changed TestlinkAPIGeneric and TestlinkAPIClient api methods
+
+- createPlatform() is adapted to support new optional boolean arguments
+ and
+ - When they are not set to , assignTestCaseExecutionTask() might fail
+ with an error like
+ - TLResponseError: 3041: Test plan (name:TestPlan_API A) has no platforms linked
+
+TestLink-API-Python-client v0.8.1-fix131 (Mar. 2020)
+------------------------------------------------------------
+
+fix missing supported API 1.9.17 interfaces
+- Pull request #131 by heuy - add closeBuild api function
+
+TestLink-API-Python-client v0.8.1 (Aug. 2019)
+------------------------------------------------------------
+support for TL 1.9.20 (dev) release and py27, py36, py37
+
+main topic is to support TL 1.9.19 and TL 1.9.20 (dev) api changes related to
+test case attachments, which are stored since TL 1.9.19 with a reference to
+the test case version instead the test case id .
+
+Parameter is now mandatory for _uploadTestCaseAttachment_ and optional
+for _getTestCaseAttachments_.
+
+implement other 1.9.20 (dev) changed api interfaces - #122
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+changed TestlinkAPIGeneric and TestlinkAPIClient api methods
+
+- getTestCasesForTestPlan() is adapted to support the new optional argument .
+ Sample see ``_
+
+known TL 1.9.19 issue:
+~~~~~~~~~~~~~~~~~~~~~~~
+API-XMLRPC - getTestCaseAttachments returns no attachment, uploaded with uploadTestCaseAttachment
+
+- see TL Mantis Ticket 8658 `_
+- recommended to use the TL 1.9.20 development state with github commit
+ 6a4984164 or later (even for the TL upgrade or installation).
+
+known TL 1.9.20 development state issue (github commit a1c7aca97):
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Calling getTestCasesForTestPlan() with the new optional argument returns only internal db values.
+As alternative, loop over all returned test cases and call getTestCaseCustomFieldDesignValue().
+Sample see ``_
+
+TestLink-API-Python-client v0.8.0 (May. 2018)
+---------------------------------------------
+support for TL 1.9.17 release and py27, py36
+
+
+implement 1.9.17 new api interfaces - #76, #81, #82, #83, #101
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- getExecutionSet(, [testcaseid=], [testcaseexternalid=],
+ [buildid=], [buildname=], [platformid=],
+ [platformname=], [options=], [devKey=])
+- getRequirements(, [testplanid=], [platformid=], [devKey=])
+- getReqCoverage(, , [devKey=])
+- setTestCaseTestSuite(, , [devKey=])
+- getTestSuiteAttachments(, [devKey=])
+- getAllExecutionsResults(, [testcaseid=],
+ [testcaseexternalid=], [platformid=],
+ [buildid=], [options=])
+
+TestReporter and other improvements pull request #94
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+pull request by Brian-Williams:
+
+- TestReporter's subclasses can be used to idempotently generate required
+ components of testlink before sending the report to a specified testcase.
+- TestGenReporter is a default combination of all the TestReporter's subclasses
+ and will try to generate everything it's subclasses is capable of.
+- Added TestLinkHelper._setParams to simplify libraries that need to overwrite
+ how a helper aquires it's parameters
+- Added TestlinkAPIClient.getTestCaseByVersion to expose the common need of
+ gettting the latest testcase by default. It was already used, but not a
+ function in this class and has uses elsewhere.
+
+``_ includes sample, how to use
+TestGenReporter and TestlinkAPIClient.getTestCaseByVersion
+
+Self signed / Let's Encrypt SSL certificate support #90
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+TestlinkAPIGeneric and TestlinkAPIClient accepts know the additional
+xmlrpclib.ServerProxy() arguments *use_datetime* and *context* (new with Py2.7.9)
+- https://docs.python.org/2/library/xmlrpclib.html
+
+*context* allows to define a SSL context, which can holds various data
+longer-lived than single SSL connections, such as SSL configuration options,
+certificate(s) and private key(s). (new with Py2.7.9)
+- https://docs.python.org/2/library/ssl.html#ssl-contexts
+
+TestLinkHelper will set the *unverified_context()* if the server url starts with
+*https* and no *context* is defined, calling
+- TestLinkHelper().connect(TestlinkAPIClient)
+
+
+known TL 1.9.17 issues:
+~~~~~~~~~~~~~~~~~~~~~~~
+API-XMLRPC - new 1.9.17 xmlrpc.class function getAllExecutionsResults not callable via XMLRPC api
+
+- see `TL Mantis Ticket 8259 `_
+
+fixed TL 1.9.17-DEV issues:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+API-XMLRPC - getRequirements raise PHP Fatal error (github commit 0c8feb6)
+
+- see `TL Mantis Ticket 7902 `_
+
+API-XMLRPC - getExecutionSet raise database error (github commit 1ee5f78)
+
+- see `TL Mantis Ticket 7900 `_
+
+TestLink-API-Python-client release notes v0.6.4 (Mar. 2017)
+-----------------------------------------------------------
+support for TL 1.9.16 release and py27, py34, py35 and py36
+
+implement 1.9.16 new api interfaces - #80
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+new TestlinkAPIGeneric and TestlinkAPIClient api method
+
+- updateBuildCustomFieldsValues(, , ,
+ , [devKey=])
+
+example ``_ shows, how to set and get
+customer field values
+
+TestLink-API-Python-client release notes v0.6.3 (Nov. 2016)
+-----------------------------------------------------------
+support for TL 1.9.15 release and py26, py27, py33, py34 and py35
+
+- further releases will be developed only against py27, py34 and py35
+- If there is a need to support other py versions, please give feedback
+
+implement 1.9.15 new api interfaces - #54 #67 #69
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+new TestlinkAPIGeneric and TestlinkAPIClient api methods
+
+- updateTestSuite(, [testprojectid=],
+ [prefix=], [parentid=], [testsuitename=],
+ [details=], [order=], [devKey=])
+- getTestSuite(, , [devKey=])
+- getIssueTrackerSystem(, [devKey=])
+
+implement 1.9.15 changed api interfaces - #68 #70 #72 #71 #69
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+changed TestlinkAPIGeneric and TestlinkAPIClient api methods
+
+- reportTCResult() is adapted to support the new optional argument
+ for setting test step results
+- createBuild() is adapted to support new optional arguments
+
+ - : 1 (default) = activ 0 = inactiv
+ - : 1 (default) = open 1 = closed
+ - : YYYY-MM-DD
+ - : valid buildid tester assignments will be copied.
+
+- addTestCaseToTestPlan() is adapted to to support the new optional argument
+ to update linked Test Case Versions
+- createTestCase() is adapted to to support the new optional arguments
+ and
+- createTestProject() is adapted to to support the new optional arguments
+ and to link a project with an ITS
+
+examples:
+
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tls.reportTCResult(None, 'aTPlanID', 'aBuildName', 'f', 'result note',
+ >>> testcaseexternalid='aTCaseFullExID', overwrite=True,
+ >>> platformname='Small Birds', execduration=4.1,
+ >>> timestamp='2015-09-19 14:33:02',
+ >>> steps=[{'step_number' : 3, 'result' : 'p', 'notes' : 'a exec note3'},
+ >>> {'step_number' : 4, 'result' : 'f', 'notes' : 'a exec note4'}])
+ >>> tls.createBuild(aTPlanID, 'newBuildName', 'a build note',
+ >>> active=1, open=1, releasedate='2016-11-30'
+ >>> copytestersfrombuild=existingBuildID)
+ >>> tls.addTestCaseToTestPlan(aTProjectID, aTPlanID, 'aTCaseFullExID',
+ >>> aTCVersion, overwrite=1)
+
+
+known TL 1.9.15 issues:
+~~~~~~~~~~~~~~~~~~~~~~~
+
+changing test suite order with updateTestSuite raise internal server error
+
+- same reason as `TL Mantis Ticket 7696 `_
+- solution: change *testlink-1.9.15/lib/functions/testsuite.class.php - update* as
+ descriped in `TL GitHub Commit 1fa41e7 `_
+
+TestLink web presents no login page (internal server error)
+
+- see `TL Mantis Ticket 7708 `_
+- solution: change *testlink-1.9.15/lib/functions/common.php* as described in `TL GitHub Commit db74644 `_
+
+Test projects with execution step results can not be deleted
+- details and solution see `TL Mantis Ticket 7765 `_
+
+TestLink-API-Python-client v0.6.2 release notes v0.6.2 (Oct. 2015)
+------------------------------------------------------------------
+support for TL 1.9.14 release and py26, py27, py33 and py34
+
+- further releases will be developed only against py27 and py34.
+- If there is a need to support other py versions, please give feedback
+
+implement 1.9.14 new api interfaces - #53 #61
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+new TestlinkAPIGeneric and TestlinkAPIClient api methods
+
+- deleteTestProject()
+- updateTestSuiteCustomFieldDesignValue(, , )
+
+examples ``_ and
+``_ now deletes the sample project, if it
+already exist.
+
+example ``_ shows, how to set and get
+customer field values
+
+implement 1.9.14 changed api interfaces - #48 #49 #54 #59
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+changed TestlinkAPIGeneric and TestlinkAPIClient api methods
+
+- addTestCaseKeywords() and removeTestCaseKeywords() are adapted to work with a set of keywords.
+
+- getTestCaseKeywords() is adapted to work with a set of test cases ids.
+
+- createTestPlan() is adapted to work with new optional argument
+
+- reportTCResult() is adapted to work with new optional arguments
+ and
+
+examples:
+
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tls.addTestCaseKeywords( {TCa_exID : ['KW01', 'KW03', 'KW02'],
+ >>> TCb_exID : ['KW01', 'KW02', 'KW04']})
+ >>> tls.removeTestCaseKeywords( {TCa_exID : ['KW02'],
+ >>> TCb_exID : ['KW01', 'KW04']})
+ >>> tls.getTestCaseKeywords( testcaseid=[TCa_ID, TCb_ID] )
+ >>> tls.getTestCaseKeywords( testcaseexternalid=[TCa_exID, TCb_exID] )
+ >>> tls.createTestPlan('aTPlanName', 'aTProjectName')
+ >>> tls.createTestPlan('aTPlanName', testprojectname='aTProjectName')
+ >>> tls.createTestPlan('aTPlanName', prefix='aTProjectPrefix')
+ >>> tls.reportTCResult(None, 'aTPlanID', 'aBuildName', 'f', 'result one',
+ >>> testcaseexternalid='aTCaseFullExID', overwrite=True,
+ >>> platformname='Small Birds', execduration=4.1,
+ >>> timestamp='2015-09-19 14:33:02')
+
+Attention:
+the api getTestCaseKeywords() returns for the situation **invalid test case id**
+a different error code
+
+- 1.9.13 error code *5000* - 1.9.14 error code *5040*
+
+Bugfixes TestLink-API-Python-client v0.6.1 - #51 #55 #56 #45
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+whatArgs reports incorrect arg name for createTestCase
+
+- wrong arg name , correct arg name
+
+TestlinkAPIClient service method countProjects() raise 'Empty Response!' error,
+when no project exist
+
+- general problem of all 'count*' service methods
+- api method 'getProjects()' now returns an empty list when no projects exists
+
+TestlinkAPIClient does not accept optional argument 'transport' for proxy
+configuration
+
+- *TestlinkAPIClient* accepts now like *TestlinkAPIGeneric* optional arguments
+
+TestlinkAPIClient service method listKeywordsForTC() uses now getTestCaseKeywords()
+
+- internal change to reduce code complexity
+
+Known TL 1.9.14 limitations:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- 7282 updateTestSuiteCustomFieldDesignValue() does change customer field values
+
+TestLink-API-Python-client release notes v0.6.1 (Mar. 2015)
+-----------------------------------------------------------
+support for TL 1.9.13 release
+
+Proxy configuration support in TestLinkHelper - pull request #36
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Integrates `Maberi `_
+pull request `#36 `_
+
+- allows easy proxy configuration using TestLinkHelper
+- Adds a new --proxy option in command line.
+- Recognizes "http_proxy" environment variable.
+
+implement 1.9.13 new api methods #32 #41 #42 #44 #47 #46
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+new TestlinkAPIGeneric and TestlinkAPIClient api methods
+
+- unassignTestCaseExecutionTask(, ,
+ [buildid=], [buildname=], [platformid=],
+ [platformname=], [user=],
+ [action='unassignAll'|'unassignOne'], [devKey=])
+
+- getProjectKeywords()
+
+- getTestCaseKeywords([testcaseid=],
+ [testcaseexternalid=])
+
+- deleteTestPlan()
+
+- addTestCaseKeywords(, )
+ Attention: with TL 1.9.14, this api method will change the interface (args)
+ see `TL Mantis Task 6934 `_
+
+- removeTestCaseKeywords(, )
+ Attention: with TL 1.9.14, this api method will change the interface (args)
+ see `TL Mantis Task 6907 `_
+
+
+examples see ``_ and ``_
+
+implement 1.9.13 api change - getTestCasesForTestPlan #41
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+TestlinkAPIGeneric and TestlinkAPIClient api method getTestCasesForTestPlan()
+accepts now the additional optional argument platformid=
+
+example:
+
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tls.getTestCasesForTestPlan(aTPlanID, platformid=aPlatFormID)
+ {'12996': {'949': {'platform_name': 'Small Bird', ... }}
+
+Also the optional argument buildid= could now be used
+
+
+TestLink-API-Python-client release notes v0.6.0 (Dec. 2014)
+-----------------------------------------------------------
+
+support for TestLink release 1.9.12 and py26, py27, py33 and py34
+
+python 3 support - pull requests #33 #37
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Integrates `manojklm `_
+pull requests
+`#33 `_
+and `#37 `_
+
+- add source and unittest support for py33 and py34
+- extend py26 support for unittest2
+- add *.travis.yml* configuration for `Travis CI `_
+- add *tox.ini* configuration for `Tox `_
+
+Track now TestLink-API-Python-client build results on Travis CI - see
+https://travis-ci.org/lczub/TestLink-API-Python-client
+
+extend upload attachments - handling file path #40
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+uploading attachments excepts now also a file path as parameter.
+
+still supported 0.5.2 behavior - file descriptor::
+
+ a_file_obj=open(A_VALID_FILE_PATH)
+ newAttachment = myTestLink.uploadExecutionAttachment(a_file_obj, A_Result_ID,
+ 'Attachment Title', 'Attachment Description')
+
+new supported 0.6.0 behaviour - file path::
+
+ a_file_path=A_VALID_FILE_PATH
+ newAttachment = myTestLink.uploadExecutionAttachment(a_file_path, A_Result_ID,
+ 'Attachment Title', 'Attachment Description')
+
+TestLink-API-Python-client release notes v0.5.2 (Oct. 2014)
+-----------------------------------------------------------
+support for TestLink release 1.9.12
+
+implement 1.9.12 new api method - getTestCaseAssignedTester #29
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+new TestlinkAPIGeneric and TestlinkAPIClient api method
+
+- getTestCaseAssignedTester(, ,
+ [buildid=], [buildname=], [platformid=],
+ [platformname=], [devKey=])
+
+examples see ``_
+
+implement 1.9.12 new api method - getTestCaseBugs #30
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+new TestlinkAPIGeneric and TestlinkAPIClient api method
+
+- getTestCaseBugs(,
+ [testcaseid]=], [testcaseexternalid=],
+ [buildid=], [buildname=], [platformid=],
+ [platformname=], [devKey=])
+
+examples see ``_
+
+TestLink-API-Python-client release notes v0.5.1 (Aug. 2014)
+-----------------------------------------------------------
+support for TestLink release 1.9.11
+
+implement 1.9.11 api change - getLastExecutionResult #27
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+TestlinkAPIGeneric and TestlinkAPIClient api method getLastExecutionResult()
+accepts now following additional optional arguments
+
+- options = {'getBugs' : True / False}
+
+example:
+
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tls.getLastExecutionResult(aTPlanID, aTCaseID, options={'getBugs' : True})
+ [{ ... , 'tcversion_id': '8929', ... , 'bugs': [{'bug_id': '4711'}], ... }]
+
+implement 1.9.11 new api method - assignTestCaseExecutionTask #26
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+new TestlinkAPIGeneric and TestlinkAPIClient api method
+
+- assignTestCaseExecutionTask(, , ,
+ [buildid=], [buildname=], [platformid=],
+ [platformname=], [devKey=])
+
+examples see ``_
+
+
+TestLink-API-Python-client release notes v0.5.0 (Jul. 2014)
+-----------------------------------------------------------
+support for TestLink release 1.9.10
+
+new service methods - list keywords #25
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+new TestlinkAPIClient service methods, which returns keyword lists without
+internal details (like getTestCasesForTestSuite() does)
+
+- listKeywordsForTC(internal_or_external_tc_id)
+- listKeywordsForTC(internal_ts_id)
+
+Example::
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tc_kw = tls.listKeywordsForTC('NPROAPI-3')
+ ['KeyWord01', 'KeyWord03']
+ >>> tc_kw = tls.listKeywordsForTC(5440)
+ ['KeyWord01', 'KeyWord03']
+ >>> tc_kw = tls.listKeywordsForTC('5440')
+ ['KeyWord01', 'KeyWord03']
+ >>> ts_kw = tls.listKeywordsForTS('5415')
+ {'5440' : ['KeyWord01', 'KeyWord03'], '5445' : ['KeyWord03'], '5450' : []}
+
+
+Known limitations:
+
+- it is not possible to ask for a special test case version, cause TL links
+ keywords against a test case and not a test case version
+
+implement 1.9.10 api change - getTestCasesForTestSuite #23
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+TestlinkAPIGeneric and TestlinkAPIClient api method getTestCasesForTestSuite()
+accepts now following additional optional arguments (usable with TL >= 1.9.10)
+
+- parameter getkeywords
+
+implement 1.9.10 api change - reportTCResult #24
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+TestlinkAPIGeneric and TestlinkAPIClient api method reportTCResult()
+accepts now following additional optional arguments (usable with TL >= 1.9.10)
+
+- user
+
+
+implement missing 1.9.8 api method - CustomField #12
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+new TestlinkAPIGeneric and TestlinkAPIClient api methods
+
+- updateTestCaseCustomFieldDesignValue, getTestCaseCustomFieldExecutionValue
+ getTestCaseCustomFieldTestPlanDesignValue
+- getTestSuiteCustomFieldDesignValue, getTestPlanCustomFieldDesignValue
+- getReqSpecCustomFieldDesignValue , getRequirementCustomFieldDesignValue
+
+
+TestLink-API-Python-client release notes v0.4.8 (Mar. 2014)
+-----------------------------------------------------------
+
+add Python 2.6 support #21
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Installation failed under Python 2.6 with on error, cause TestlinkAPIGeneric
+used in *_convertPostionalArgs()* a
+`Py31 feature, back ported to Py27 `_
+
+- Dictionary and set comprehensions ({i: i*2 for i in range(3)}).
+
+TestLink-API-Python-client is now installable under Py26 and Py27.
+To use it under Py26, the module *argparse* must be installed additionally::
+
+ pip install argparse
+ pip install TestLink-API-Python-client
+
+
+implement 1.9.9 api changes - getLastExecutionResult #16
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+TestlinkAPIGeneric and TestlinkAPIClient api method getLastExecutionResult()
+accepts now following additional optional arguments (usable with TL >= 1.9.9)
+
+- platformid, platformname, buildid, buildname
+
+implement missing 1.9.9 api method - testLinkVersion #16
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+new TestlinkAPIGeneric and TestlinkAPIClient api method to return the TL version
+
+- testLinkVersion()
+
+new TestlinkAPIGeneric and TestlinkAPIClient service method to return connection informations
+
+- connectionInfo()
+
+implement missing 1.9.8 api method - miscellaneous #14
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+new TestlinkAPIGeneric and TestlinkAPIClient api methods
+
+- getUserByLogin(), getUserByID()
+- deleteExecution()
+- setTestCaseExecutionType()
+- assignRequirements()
+- getExecCountersByBuild()
+
+Known TL 1.9.9 limitations:
+
+- 6202 assignRequirements() calls assign_to_tcase() without author_id
+- 6197 MSSQL - 1.9.8 Upgrade - req_coverage table
+- 6193 POSTGRESQL - 1.9.8 Upgrade - req_coverage table
+
+TestLink-API-Python-client release notes v0.4.7 (Jan. 2014)
+-----------------------------------------------------------
+
+new service methods - copy test cases #17
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+new TestlinkAPIClient service methods to copy test cases between test suites or
+to create a new test case version.
+
+- copyTCnewVersion(origTestCaseId, \*\*changedAttributes)
+- copyTCnewTestCase(origTestCaseId, \*\*changedAttributes)
+- getProjectIDByNode(a_nodeid)
+
+Example::
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3')
+ [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2',
+ 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}]
+ >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], testsuiteid=newSuiteID,
+ testcasename='a new test case name')
+
+Known limitations:
+
+- estimatedexecduration settings are not copied
+
+implement missing 1.9.8 api methods - TestCase #11
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+new TestlinkAPIGeneric and TestlinkAPIClient api methods to modify test cases
+
+- addTestCaseToTestPlan, updateTestCase
+- createTestCaseSteps, deleteTestCaseSteps
+
+Known TL 1.9.9 limitations:
+
+- 6109 createTestCaseSteps with action *update* does not change existing steps
+- 6108 createTestCaseSteps creates steps without test case references
+- 6102 updateTestCase returns debug informations
+- 6101 updateTestCase does not set modification timestamp
+
+implement missing 1.9.8 api methods - Attachments #13
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+new TestlinkAPIGeneric and TestlinkAPIClient api methods to upload attachments
+
+- uploadRequirementSpecificationAttachment, uploadRequirementAttachment
+- uploadTestProjectAttachment, uplodTestSuiteAttachment
+- uploadTestCaseAttachment
+
+TestLink-API-Python-client release notes v0.4.6 (Dec. 2013)
+-----------------------------------------------------------
+
+TestLink-API-Python-client is now installable via PyPI #15
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ pip install TestLink-API-Python-client
+
+new api methods for Platforms implemented #10
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+new TestlinkAPIGeneric and TestlinkAPIClient api methods to handle platforms
+
+- createPlatform, getProjectPlatforms
+- addPlatformToTestPlan, removePlatformFromTestPlan
+
+Known TL 1.9.9 limitations:
+
+- 6076 addPlatformToTestPlan creates invalid platform links
+
+TestLink-API-Python-client release notes v0.4.5 (Nov. 2013)
+-----------------------------------------------------------
+
+All v0.4.0 API methods from TestlinkAPIClient are shifted to the new super class
+TestlinkAPIGeneric and could be used with the new optional argument handling and
+asked with whatArgs() for there arguments.
+
+- getProject, createTestProject, createTestCase, createTestSuite, createTestPlan,
+ createTestCase
+- createBuild, reportTCResult, uploadExecutionAttachment,
+- getTestProjectByName, getProjectTestPlans, getTotalsForTestPlan, getBuildsForTestPlan
+- getLatestBuildForTestPlan, getTestPlanByName
+- getTestSuitesForTestPlan, getTestSuiteByID, getTestSuitesForTestSuite,
+ getFirstLevelTestSuitesForTestProject
+- getTestCasesForTestSuite, getTestCasesForTestPlan, getTestCaseIDByName, getFullPath
+- getLastExecutionResult, getTestCaseCustomFieldDesignValue, getTestCaseAttachments
+
+Other API methods can be used with the new method
+
+- callServerWithPosArgs(apiMethodame, [apiArgName=apiArgValue])
+
+generic api class TestlinkAPIGeneric #7
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+new class TestlinkAPIGeneric implements the Testlink API methods as generic PY methods
+
+- all arguments of Teslink API are supported as optional arguments
+- often used (or mandatory) arguments can be configured as positional arguments
+- error handling for TestLink API error codes
+
+class TestlinkAPIClient inherits now from TestlinkAPIGeneric the Testlink API methods
+
+- configuration for positional arguments are consistent with v0.4.0
+ - except getTestCaseIDByName (see ac6ccf5)
+
+Attention - handling for optional arguments has been changed. Existing code,
+which uses TestlinkAPIClient, must be adapted. Changes between v0.4.5 and v.0.4.0
+are documented in `example/TestLinkExample.py`
+
+public API method callServerWithPosArgs() #4
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Every implemented API method uses the new method callServerWithPosArgs() to call
+the server and check the response for error codes.
+
+- If the response include an error code, a TLResponseError is raised
+
+This method can although be used to call not yet implemented API methods.
+
+helper method .whatArgs(apiMethodName) #8
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The Teslink API Client can now be asked, what arguments a API method expects::
+
+ import testlink
+ tlh = testlink.TestLinkHelper()
+ tls = tlh.connect(testlink.TestlinkAPIClient)
+ print tls.whatArgs('createTestPlan')
+ createTestPlan(, , [note=], [active=], [public=], [devKey=])
+ create a test plan
+
+or for a description of all implemented api method ::
+
+ import testlink
+ tlh = testlink.TestLinkHelper()
+ tls = tlh.connect(testlink.TestlinkAPIClient)
+ for m in testlink.testlinkargs._apiMethodsArgs.keys():
+ print tls.whatArgs(m), '\n'
+
+other changes
+~~~~~~~~~~~~~
+
+see `Milestone v0.4.5 `_
diff --git a/MANIFEST.in b/MANIFEST.in
index fb3515b..af5202e 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,7 @@
-include MANIFEST.in LICENSE-2.0.txt README.md
+include MANIFEST.in LICENSE-2.0.txt README.rst CHANGES.rst tox.ini
recursive-include src *.py
recursive-include example *.py
+recursive-include example *.png
+recursive-include example *.xml
+recursive-include test *.py
+recursive-include doc *.rst
diff --git a/README.md b/README.md
deleted file mode 100644
index f8fd9c6..0000000
--- a/README.md
+++ /dev/null
@@ -1,95 +0,0 @@
-TestLink API Python Client
-==========================
-
-Copyright 2011-2012
-James Stock, Olivier Renault, TestLink-API-Python-client developers
-
-License [Apache License 2.0]
-
-Introduction
-------------
-
-TestLink-API-Python-client is a Python XMLRPC client for the [TestLink API].
-
-It's a brick of Olivier Renault [JinFeng] idea - an interaction of [TestLink],
- [Robot Framework] and [Jenkins].
-
-Initially based on the James Stock testlink-api-python-client R7.
-
-
-Directory Layout
-----------------
-
-src/
-* Source for TestLink API Python Client
-
-tests/
-* Unit Tests for TestLink API Python Client
-
-examples/
-* Examples, how to use the TestLink API Python Client
-
-Installation
-------------
-
-### Install TestLinkAPI into a virtualenv environment
-
-```
-[PYTHON27]\Scripts\virtualenv [PYENV]\testlink
-[PYENV]\testlink\Scripts\activate
-python setup.py install
-```
-
-### Run example with command line arguments
-
-```
-[PYENV]\testlink\Scripts\activate
-python example\TestLinkExample.py
- --server_url http://[YOURSERVER]/testlink/lib/api/xmlrpc.php
- --devKey [Users devKey generated by TestLink]
-```
-
-### Run unittests with TestLink Server interaction
-
-```
-[PYENV]\testlink\Scripts\activate
-set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc.php
-set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink]
-cd test\utest
-python -m unittest testlinkapicallservertest testlinkapi_online_test
-```
-
-### Run unittests without TestLink Server interaction
-
-```
-[PYENV]\testlink\Scripts\activate
-cd test\utest
-python -m unittest testlinkhelpertest testlinkapi_offline_test
-```
-
-
-Download
---------
-
-see [downloads]
-
-
-TestLink-API-Python-client developers
--------------------------------------
-* [James Stock], [Olivier Renault], kereval.com
-* [g4l4drim], [pade], [lczub]
-* anyone forgotten?
-
-
-[Apache License 2.0]: http://www.apache.org/licenses/LICENSE-2.0
-[JinFeng]: http://www.sqaopen.net/blog/en/?p=63
-[TestLink API]: http://www.teamst.org/_tldoc/1.8/phpdoc_api/TestlinkAPI/TestlinkXMLRPCServer.html
-[TestLink]: http://www.teamst.org/
-[Robot Framework]: http://code.google.com/p/robotframework
-[Jenkins]: http://jenkins-ci.org/
-[downloads]: https://github.com/lczub/TestLink-API-Python-client/downloads
-[Olivier Renault]: https://github.com/orenault/TestLink-API-Python-client
-[pade]: https://github.com/pade/TestLink-API-Python-client
-[g4l4drim]: https://github.com/g4l4drim/TestLink-API-Python-client
-[James Stock]: https://code.google.com/p/testlink-api-python-client/
-[lczub]: https://github.com/lczub/TestLink-API-Python-client
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..d8f5856
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,121 @@
+TestLink API Python Client
+==========================
+
+Copyright 2011-2022
+James Stock, Olivier Renault, Luiko Czub, TestLink-API-Python-client developers
+
+License `Apache License 2.0`_
+
+Introduction
+------------
+
+TestLink-API-Python-client is a Python XML-RPC client for TestLink_.
+
+Initially based on James Stock testlink-api-python-client R7 and Olivier
+Renault JinFeng_ idea - an interaction of TestLink_, `Robot Framework`_ and Jenkins_.
+
+TestLink-API-Python-client delivers two main classes
+
+- TestlinkAPIGeneric - Implements the TestLink API methods as generic PY methods
+ with error handling
+- TestlinkAPIClient - Inherits from TestlinkAPIGeneric and defines service
+ methods like "copyTCnewVersion".
+
+and the helper class
+
+- TestLinkHelper - search connection parameter from environment variables and
+ command line arguments
+
+How to talk with TestLink in a python shell and copy a test case: ::
+
+ set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php
+ set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink]
+ python
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tls.countProjects()
+ 3
+ >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3')
+ [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2',
+ 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}]
+ >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], testsuiteid=newSuiteID,
+ testcasename='a new test case name')
+ >>> print tls.whatArgs('createTestPlan')
+ createTestPlan(, , [note=], [active=],
+ [public=], [devKey=])
+ create a test plan
+
+Installation
+------------
+
+Install the latest stable release from PyPI using pip by running
+
+ pip install TestLink-API-Python-client
+
+Directory Layout
+----------------
+
+src/
+ Source for TestLink API Python Client
+
+doc/
+ `Installation`_ and `Usage (EN)`_ / `Usage (FR)`_ documentation
+
+examples/
+ Examples, how to use `TestlinkAPIGeneric`_ and `TestlinkAPIClient`_.
+ For (nearly all) implemented API methods you find an example, how to
+ call it and how the response looks like.
+
+tests/
+ Unit Tests for TestLink API Python Client
+
+`tox.ini`_
+ Configuration for multi Python version testing via `Tox`_
+
+
+Help
+----
+
+Questions, Enhancements, Issues are welcome under `Issues`_
+
+Take a look into ``_ for additional details, what have changed and
+how existing code can be adapted
+
+
+TestLink-API-Python-client developers
+-------------------------------------
+* `James Stock`_, `Olivier Renault`_, `lczub`_, `manojklm`_ (PY3)
+* `g4l4drim`_, `pade`_, `anton-matosov`_, `citizen-stig`_, `charz`_, `Maberi`_
+* `Brian-Williams`_, `alexei-drozdov`_, `janLo`_, `heuj`_, `elapfra`_
+* `Mikycid`_, anyone forgotten?
+
+.. _Apache License 2.0: http://www.apache.org/licenses/LICENSE-2.0
+.. _TestLink: http://testlink.org
+.. _JinFeng: http://www.sqaopen.net/blog/en/?p=63
+.. _Robot Framework: http://code.google.com/p/robotframework
+.. _Jenkins: http://jenkins-ci.org
+.. _Installation: doc/install.rst
+.. _Usage (EN): doc/usage.rst
+.. _Usage (FR): doc/fr_usage.rst
+.. _TestlinkAPIGeneric: example/TestLinkExampleGenericApi.py
+.. _TestlinkAPIClient: example/TestLinkExample.py
+.. _tox.ini: tox.ini
+.. _Tox: http://tox.readthedocs.org/en/latest/
+.. _Issues: https://github.com/lczub/TestLink-API-Python-client/issues
+.. _Olivier Renault: https://github.com/orenault/TestLink-API-Python-client
+.. _pade: https://github.com/pade/TestLink-API-Python-client
+.. _g4l4drim: https://github.com/g4l4drim/TestLink-API-Python-client
+.. _James Stock: https://code.google.com/p/testlink-api-python-client/
+.. _lczub: https://github.com/lczub/TestLink-API-Python-client
+.. _anton-matosov: https://github.com/anton-matosov/TestLink-API-Python-client
+.. _citizen-stig: https://github.com/citizen-stig/TestLink-API-Python-client
+.. _charz: https://github.com/charz/TestLink-API-Python-client.git
+.. _manojklm: https://github.com/manojklm/TestLink-API-Python-client
+.. _Maberi: https://github.com/Maberi/TestLink-API-Python-client
+.. _Brian-Williams: https://github.com/Brian-Williams/TestLink-API-Python-client
+.. _alexei-drozdov: https://github.com/alexei-drozdov/TestLink-API-Python-client
+.. _janLo: https://github.com/janLo/TestLink-API-Python-client
+.. _heuj: https://github.com/heuj/TestLink-API-Python-client
+.. _elapfra: https://github.com/elapfra/TestLink-API-Python-client
+.. _Mikycid: https://github.com/Mikycid/TestLink-API-Python-client
\ No newline at end of file
diff --git a/doc/fr_usage.rst b/doc/fr_usage.rst
new file mode 100644
index 0000000..366e94a
--- /dev/null
+++ b/doc/fr_usage.rst
@@ -0,0 +1,229 @@
+TestLink-API-Python-client Usage
+================================
+
+.. contents::
+ :local:
+
+Comment communiquer avec testlink dans une interface système python
+-------------------------------------------
+
+Se connecter à TestLink, compter les projets existants et récupérer les données d'un cas de test: ::
+
+ [PYENV]\testlink\Scripts\activate
+ set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php
+ set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink]
+ python
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tls.countProjects()
+ 3
+ >>> tls.getTestCase(None, testcaseexternalid='NPROAPI3-1')
+ [{'full_tc_external_id': 'NPROAPI3-1', 'node_order': '0', 'is_open': '1', 'id': '2757', ...}]
+
+Demander au TestLink API Client quels arguments attend une des méthodes de l'API: ::
+
+ import testlink
+ tlh = testlink.TestLinkHelper()
+ tls = tlh.connect(testlink.TestlinkAPIClient)
+ print tls.whatArgs('createTestPlan')
+ > createTestPlan(, , [note=], [active=],
+ [public=], [devKey=])
+ > create a test plan
+
+Générer une description de toutes les méthodes implémentées par l'API: ::
+
+ import testlink
+ tlh = testlink.TestLinkHelper()
+ tls = tlh.connect(testlink.TestlinkAPIClient)
+ for m in testlink.testlinkargs._apiMethodsArgs.keys():
+ print(tls.whatArgs(m), '\n')
+
+Copier les cas de test
+---------------
+
+Copier un cas de test dans une autre suite en changeant son nom::
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3')
+ [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2',
+ 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}]
+ >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], testsuiteid=newSuiteID,
+ testcasename='a new test case name')
+
+Créer une nouvelle version d'un cas de test en changeant sa description et son importance::
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3')
+ [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2',
+ 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}]
+ >>> tls.copyTCnewVersion(tc_info[0]['testcase_id'], summary='new summary',
+ importance='1')
+
+
+Par défaut, la dernière version d'un cas de test sera utilisée pour la copie.
+Si une autre version doit être copiée, il est possible de spécifier la version
+attendue en tant que deuxième argument. Example::
+
+ >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], 1, testsuiteid=newSuiteID,
+ testcasename='a new test case name')
+ >>> tls.copyTCnewVersion(tc_info[0]['testcase_id'], 1, summary='new summary',
+ importance='1')
+
+Rapporter les résultats du test
+-------------------
+
+En utilisant la classe TestlinkAPIClient - exemple d'un cas de test échoué
+sans auteur (l'argument 'user' n'est utilisable qu'à partir d'une version
+de TestLink de 1.9.10 ou supérieure):
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tls.reportTCResult(a_TestCaseID, a_TestPlanID, 'a build name', 'f',
+ 'some notes',
+ user='a user login name', platformid=a_platformID)
+
+
+En utilisant la classe TestlinkAPIGeneric - exemple d'un cas de test passé
+en utilisant un auteur (argument 'user'):
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIGeneric)
+ >>> tls.reportTCResult(a_TestPlanID, 'p', testcaseid=a_TestCaseID,
+ buildname='a build name', notes='some notes',
+ user='a login name', platformid=a_platformID)
+
+
+En utilisant la classe TestlinkAPIGeneric - exemple d'un cas de test bloqué
+sans auteur
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIGeneric)
+ >>> exTCID = tls.getTestCase(testcaseid=a_TestCaseID)[0]['full_tc_external_id']
+ >>> tls.reportTCResult(a_TestPlanID, 'b', testcaseexternalid=exTCID,
+ buildid='a build name', platformname='a platform name')
+
+Rapport de résultats de tests avec horodatage et résultat des étapes
+--------------------------------------------------
+
+Ce résultat de test utilise son id externe (testcaseexternalid), et non l'id interne (testcaseid)
+
+- Les arguments 'execduration' et 'timestamp' requièrent une version de
+ TestLink de 1.9.14 ou supérieure
+- L'argument 'steps' requiert une version de TestLink de 1.9.15 ou supérieure
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tls.reportTCResult(None, newTestPlanID_A, None, 'f', '', guess=True,
+ testcaseexternalid=tc_aa_full_ext_id, platformname=NEWPLATFORM_A,
+ execduration=3.9, timestamp='2015-09-18 14:33',
+ steps=[{'step_number' : 6, 'result' : 'p', 'notes' : 'result note for passed step 6'},
+ {'step_number' : 7, 'result' : 'f', 'notes' : 'result note for failed step 7'}] )
+
+Envoyer des pièces jointes
+------------------
+
+Télécharger des pièces jointes peut être fait de deux différentes manières:
+
+Avec un descripteur de fichier :
+
+ a_file_obj=open(CHEMIN_VALIDE_VERS_LE_FICHIER)
+ newAttachment = myTestLink.uploadExecutionAttachment(a_file_obj, A_Result_ID,
+ 'Attachment Title', 'Attachment Description')
+
+
+Ou avec un chemin de fichier :
+
+ a_file_path=A_VALID_FILE_PATH
+ newAttachment = myTestLink.uploadExecutionAttachment(CHEMIN_VALIDE_VERS_LE_FICHIER, A_Result_ID,
+ 'Attachment Title', 'Attachment Description')
+
+Lister les mots-clés
+-------------
+
+En utilisant une méthode de l'API (classe TestlinkAPIGeneric) -
+Lister les mots-clés de tous les cas de test d'une suite:
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> ts_kw = tls.getTestCasesForTestSuite(SuiteID, False, 'full', getkeywords=True)
+
+
+En utilisant une méthode de l'API (classe TestlinkAPIGeneric) -
+Lister tous les mots clés d'une suite de test et ses sous-suites
+
+ >>> ts_kw = tls.getTestCasesForTestSuite(SuiteID, True, 'full', getkeywords=True)
+
+En utilisant une méthode du service (classe TestlinkAPIClient) -
+Lister tous les mots clés sans ses détails pour un cas de test
+
+ >>> tc_kw = tls.listKeywordsForTC(5440)
+ >>> tc_kw = tls.listKeywordsForTC('NPROAPI-3')
+
+En utilisant une méthode du service (classe TestlinkAPIClient) -
+Lister tous les mots clés sans ses détails pour tous les cas de test d'une suite
+
+ >>> ts_kw = tls.listKeywordsForTS('5415')
+
+
+Lancement d'un exemple
+------------
+
+Pour lancer l'exemple "comment utiliser la classe TestlinkAPIClient", en
+spécifiant les paramètres de connexion en tant qu'arguments de ligne de commande [1]_: ::
+
+ [PYENV]\testlink\Scripts\activate
+ python example\TestLinkExample.py
+ --server_url http://[YOURSERVER]/testlink/lib/api/xmlrpc.php
+ --devKey [Users devKey generated by TestLink]
+
+Pour lancer l'exemple "comment utiliser la classe TestlinkAPIGeneric", en
+spécifiant les paramètres de connexion en tant que variable d'environment [2]_: ::
+
+ [PYENV]\testlink\Scripts\activate
+ set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php
+ set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink]
+ python example\TestLinkExampleGenericApi.py
+
+.. [1] TestLinkExample.py creates a new test project NEW_PROJECT_API-[CountProjects+1].
+.. [2] TestLinkExampleGenericApi.py creates a new test project PROJECT_API_GENERIC-[CountProjects+1].
+
+Lancer des tests unitaires
+-------------
+
+Lancer des tests unitaires avec interaction du serveur de TestLink: ::
+
+ [PYENV]\testlink\Scripts\activate
+ set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc.php
+ set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink]
+ cd test\utest
+ python -m unittest discover -s test\utest-online
+
+Lancer des tests unitaires sans interaction du serveur de TestLink: ::
+
+ [PYENV]\testlink\Scripts\activate
+ cd test\utest
+ python -m unittest discover -s test\utest-offline
+
+En deca de Py26, unittest2_ doit être utilisé.
+
+.. _unittest2: https://pypi.python.org/pypi/unittest2
+
+
+Comment accéder aux données originelles d'échange de XML
+------------------------------------------
+
+Si pour des raisons de débogage les versions originelles d'échange de XML sont requises,
+il est possible d'initialiser l'API client avec le paramètre optionnel *verbose* mis à *True*: ::
+
+ >>> tlh = testlink.TestLinkHelper()
+ >>> tls = testlink.TestlinkAPIClient(tlh._server_url, tl._devkey, verbose=True)
+ send: b"POST /testlink/lib/api/xmlrpc/v1/xmlrpc.php HTTP/1.1\r\nHost: ...
+ \n\ntl.getUserByLogin\n...\n\n"
+ reply: 'HTTP/1.1 200 OK\r\n'
+ header: Date header: Server header: ... body: b'\n\n ...'
+ body: b'1\n\n \n ...'
+ body: b'... \n\n'
+
+
diff --git a/doc/install.rst b/doc/install.rst
new file mode 100644
index 0000000..92a50c6
--- /dev/null
+++ b/doc/install.rst
@@ -0,0 +1,114 @@
+TestLink-API-Python-client Installation
+=======================================
+
+.. contents::
+ :local:
+
+Preconditions
+-------------
+
+Currently the combinations Python 2.7.16 / 3.6.8 / 3.7.4 are tested with
+TestLink 1.9.20 (development state, github a1c7aca97). Other combination might work - feedback is welcome :-)
+
+TestLink configuration
+----------------------
+
+The TestLink configuration (config.inc.php or custom_config.inc.php) must have
+enabled the api interface::
+
+ $tlCfg->api->enabled = TRUE;
+
+Create the user specific devKey inside TestLink, see
+
+- My Settings -> API interface - Personal API access key [Generate a new key]
+
+Installing from PyPI
+--------------------
+
+TestLink-API-Python-client is available in the Python Package Index (PyPI_)
+since Release v0.4.6. It is recommended that you use `pip`_ to install.
+
+Install from PyPI using pip by running::
+
+ pip install TestLink-API-Python-client
+
+Installing from source distribution
+-----------------------------------
+
+The source code can be retrieved as source distribution either
+
+- as stable release from SourceForge_
+- or as development version from `GitHup Releases`_
+
+Install the archives using pip by running::
+
+ pip install TestLink-API-Python-client-0.8.1.zip
+
+Installing from source
+----------------------
+
+Install the extracted source distribution or download of the last (none tested)
+changes from `GitHup Master`_ by running::
+
+ python setup.py install
+
+Verifying Installation
+----------------------
+
+Check, if Python could talk with TestLink using the connection parameter
+
+- SERVER_URL = http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php
+- DEVKEY = [Users devKey generated by TestLink]
+
+as init parameter::
+
+ python
+ >>> import testlink
+ >>> tls = testlink.TestlinkAPIClient(SERVER_URL, DEVKEY)
+ >>> tls.about()
+ ' Testlink API Version: 1.0 initially ....'
+
+or by defining the connection parameter as environment variables::
+
+ set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php
+ set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink]
+ python
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper(testlink.TestlinkAPIClient)
+ >>> tls.about()
+ ' Testlink API Version: 1.0 initially ....'
+
+Changed SERVER_URL with TestLink 1.9.7
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The SERVER_URL path has changed with TestLink 1.9.7.
+
+- TL Version < 1.9.7: Use http://[YOURSERVER]/testlink/lib/api/xmlrpc.php
+- TL Version >=1.9.7: Use http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php
+
+Install in virtualenv TestLink
+------------------------------
+
+Cause most linux system (and although newer windows systems too) expect root
+privileges for installation, a separate TestLink virtualenv_ may help::
+
+ [PYTHON27]\Scripts\virtualenv [PYENV]\testlink
+ [PYENV]\testlink\Scripts\activate
+ pip install TestLink-API-Python-client
+
+If you always work with the same TestLink server, extend the script
+
+- [PYENV]/testlink/Scripts/activate[.bat|.ps1]
+
+with connection parameter as environment variables
+
+- TESTLINK_API_PYTHON_SERVER_URL and TESTLINK_API_PYTHON_DEVKEY
+
+
+.. _PyPI: https://pypi.python.org/pypi
+.. _pip: http://www.pip-installer.org
+.. _SourceForge: http://sourceforge.net/projects/testlink-api-python-client/files/latest/download
+.. _GitHup Releases: https://github.com/lczub/TestLink-API-Python-client/releases
+.. _GitHup Master: https://github.com/lczub/TestLink-API-Python-client/archive/master.zip
+.. _virtualenv: http://www.virtualenv.org/en/latest/virtualenv.html
+.. _issue 50: https://github.com/lczub/TestLink-API-Python-client/issues/50
diff --git a/doc/usage.rst b/doc/usage.rst
new file mode 100644
index 0000000..9468106
--- /dev/null
+++ b/doc/usage.rst
@@ -0,0 +1,220 @@
+TestLink-API-Python-client Usage
+================================
+
+.. contents::
+ :local:
+
+How to talk with TestLink in a python shell
+-------------------------------------------
+
+Connect TestLink, count existing projects and get test case data: ::
+
+ [PYENV]\testlink\Scripts\activate
+ set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php
+ set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink]
+ python
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tls.countProjects()
+ 3
+ >>> tls.getTestCase(None, testcaseexternalid='NPROAPI3-1')
+ [{'full_tc_external_id': 'NPROAPI3-1', 'node_order': '0', 'is_open': '1', 'id': '2757', ...}]
+
+Ask the TestLink API Client, what arguments a API method expects: ::
+
+ import testlink
+ tlh = testlink.TestLinkHelper()
+ tls = tlh.connect(testlink.TestlinkAPIClient)
+ print tls.whatArgs('createTestPlan')
+ > createTestPlan(, , [note=], [active=],
+ [public=], [devKey=])
+ > create a test plan
+
+or generate a description of all implemented API method: ::
+
+ import testlink
+ tlh = testlink.TestLinkHelper()
+ tls = tlh.connect(testlink.TestlinkAPIClient)
+ for m in testlink.testlinkargs._apiMethodsArgs.keys():
+ print(tls.whatArgs(m), '\n')
+
+Copy test cases
+---------------
+
+Copy an existing test case into another test suite with changed name::
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3')
+ [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2',
+ 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}]
+ >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], testsuiteid=newSuiteID,
+ testcasename='a new test case name')
+
+Create a new test case version with changed summary and importance::
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3')
+ [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2',
+ 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}]
+ >>> tls.copyTCnewVersion(tc_info[0]['testcase_id'], summary='new summary',
+ importance='1')
+
+Default is, that the last version of a test case is used for the copy.
+If a different version should be used, specify the required version as second
+argument. Examples::
+
+ >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], 1, testsuiteid=newSuiteID,
+ testcasename='a new test case name')
+ >>> tls.copyTCnewVersion(tc_info[0]['testcase_id'], 1, summary='new summary',
+ importance='1')
+
+Report test results
+-------------------
+
+Using the TestLink API Client - failed test case with none default reporter
+(argument 'users' usable with TL >= 1.9.10):
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tls.reportTCResult(a_TestCaseID, a_TestPlanID, 'a build name', 'f',
+ 'some notes',
+ user='a user login name', platformid=a_platformID)
+
+Using the TestLink Generic API Client - passed test case with none default
+reporter:
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIGeneric)
+ >>> tls.reportTCResult(a_TestPlanID, 'p', testcaseid=a_TestCaseID,
+ buildname='a build name', notes='some notes',
+ user='a login name', platformid=a_platformID)
+
+Using the TestLink Generic API Client - blocked test case with
+alternative optional args, default reporter (user for devKey)
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIGeneric)
+ >>> exTCID = tls.getTestCase(testcaseid=a_TestCaseID)[0]['full_tc_external_id']
+ >>> tls.reportTCResult(a_TestPlanID, 'b', testcaseexternalid=exTCID,
+ buildid='a build name', platformname='a platform name')
+
+Report test results with timestamp and step result
+--------------------------------------------------
+
+This test result uses the external test case id and not the internal.
+
+- argument 'execduration' and 'timestamp' usable with TL >= 1.9.14:
+- argument 'steps' usable with TL >= 1.9.15:
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tls.reportTCResult(None, newTestPlanID_A, None, 'f', '', guess=True,
+ testcaseexternalid=tc_aa_full_ext_id, platformname=NEWPLATFORM_A,
+ execduration=3.9, timestamp='2015-09-18 14:33',
+ steps=[{'step_number' : 6, 'result' : 'p', 'notes' : 'result note for passed step 6'},
+ {'step_number' : 7, 'result' : 'f', 'notes' : 'result note for failed step 7'}] )
+
+Upload attachments
+------------------
+
+uploading attachments can be done in two different ways
+
+with a file descriptor::
+
+ a_file_obj=open(A_VALID_FILE_PATH)
+ newAttachment = myTestLink.uploadExecutionAttachment(a_file_obj, A_Result_ID,
+ 'Attachment Title', 'Attachment Description')
+
+
+with a file path::
+
+ a_file_path=A_VALID_FILE_PATH
+ newAttachment = myTestLink.uploadExecutionAttachment(a_file_path, A_Result_ID,
+ 'Attachment Title', 'Attachment Description')
+
+List keywords
+-------------
+
+Using the api method - keywords for all test cases of one test suite
+
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> ts_kw = tls.getTestCasesForTestSuite(SuiteID, False, 'full', getkeywords=True)
+
+Using the api method - keywords for all test cases of a test suite and their
+sub suites
+
+ >>> ts_kw = tls.getTestCasesForTestSuite(SuiteID, True, 'full', getkeywords=True)
+
+Using the service method - keyword list without internal details for one test case
+
+ >>> tc_kw = tls.listKeywordsForTC(5440)
+ >>> tc_kw = tls.listKeywordsForTC('NPROAPI-3')
+
+Using the service method - keyword lists without internal details for all test
+cases of one test suite
+
+ >>> ts_kw = tls.listKeywordsForTS('5415')
+
+Run examples
+------------
+
+Running example, how to use the class TestlinkAPIClient, with connection
+parameter defined as command line arguments [1]_: ::
+
+ [PYENV]\testlink\Scripts\activate
+ python example\TestLinkExample.py
+ --server_url http://[YOURSERVER]/testlink/lib/api/xmlrpc.php
+ --devKey [Users devKey generated by TestLink]
+
+Running example, how to use the class TestlinkAPIGeneric, with connection
+parameter defined as environment variables [2]_: ::
+
+ [PYENV]\testlink\Scripts\activate
+ set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php
+ set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink]
+ python example\TestLinkExampleGenericApi.py
+
+.. [1] TestLinkExample.py creates a new test project NEW_PROJECT_API-[CountProjects+1].
+.. [2] TestLinkExampleGenericApi.py creates a new test project PROJECT_API_GENERIC-[CountProjects+1].
+
+Run unittests
+-------------
+
+Run unittests with TestLink Server interaction: ::
+
+ [PYENV]\testlink\Scripts\activate
+ set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc.php
+ set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink]
+ cd test\utest
+ python -m unittest discover -s test\utest-online
+
+Run unittests without TestLink Server interaction: ::
+
+ [PYENV]\testlink\Scripts\activate
+ cd test\utest
+ python -m unittest discover -s test\utest-offline
+
+Under Py26, unittest2_ must be used.
+
+.. _unittest2: https://pypi.python.org/pypi/unittest2
+
+
+How to check original exchanged XML data
+------------------------------------------
+
+If for debugging reasons the original exchanged XML data are needed, initialise
+the API client with the optional argument *verbose* set to *True*: ::
+
+ >>> tlh = testlink.TestLinkHelper()
+ >>> tls = testlink.TestlinkAPIClient(tlh._server_url, tl._devkey, verbose=True)
+ send: b"POST /testlink/lib/api/xmlrpc/v1/xmlrpc.php HTTP/1.1\r\nHost: ...
+ \n\ntl.getUserByLogin\n...\n\n"
+ reply: 'HTTP/1.1 200 OK\r\n'
+ header: Date header: Server header: ... body: b'\n\n ...'
+ body: b'1\n\n \n ...'
+ body: b'... \n\n'
+
+
diff --git a/example/Last7DaysTestCases.py b/example/Last7DaysTestCases.py
index b53a2a3..6607469 100644
--- a/example/Last7DaysTestCases.py
+++ b/example/Last7DaysTestCases.py
@@ -1,7 +1,7 @@
#! /usr/bin/python
# -*- coding: UTF-8 -*-
-# Copyright 2011-2012 Olivier Renault, James Stock, TestLink-API-Python-client developers
+# Copyright 2011-2019 Olivier Renault, James Stock, TestLink-API-Python-client developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
# which have been created during the last 7 days
+from __future__ import print_function
from testlink import TestlinkAPIClient, TestLinkHelper
import time
@@ -29,7 +30,7 @@ def iterTCasesfromTProject(api, TProjName, date1, date2):
""" returns as iterator all test cases of project TPROJTNAME, which are
created between DATE1 and DATE2
DATE1 and DATE2 must be of type time.struct_time """
- TProjId = api.getTestProjectByName(TProjName)[0]['id']
+ TProjId = api.getTestProjectByName(TProjName)['id']
for TSinfo in api.getFirstLevelTestSuitesForTestProject(TProjId):
TSuiteId = TSinfo['id']
for TCid in api.getTestCasesForTestSuite(TSuiteId, deep=1,details='only_id'):
@@ -45,8 +46,8 @@ def iterTCasesfromTProject(api, TProjName, date1, date2):
currentTime = time.localtime()
oldTime = time.localtime(time.time() - 3600 * 24 * 7)
- print '%s test cases created between %s and %s' % \
+ print('%s test cases created between %s and %s' % \
(projName, time.strftime('%Y-%m-%d', oldTime),
- time.strftime('%Y-%m-%d', currentTime))
+ time.strftime('%Y-%m-%d', currentTime)))
for TCdata in iterTCasesfromTProject(tlapi, projName, oldTime, currentTime):
- print ' %(name)s %(version)s %(creation_ts)s' % TCdata
+ print(' %(name)s %(version)s %(creation_ts)s' % TCdata)
diff --git a/example/PyGreat.png b/example/PyGreat.png
new file mode 100644
index 0000000..bcc2cbc
Binary files /dev/null and b/example/PyGreat.png differ
diff --git a/example/TestLinkExample.py b/example/TestLinkExample.py
index 88d651e..ed10ef9 100644
--- a/example/TestLinkExample.py
+++ b/example/TestLinkExample.py
@@ -1,7 +1,7 @@
#! /usr/bin/python
# -*- coding: UTF-8 -*-
-# Copyright 2011-2012 Olivier Renault, TestLink-API-Python-client developers
+# Copyright 2011-2021 Olivier Renault, Luiko Czub, TestLink-API-Python-client developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -45,12 +45,24 @@
--------- Test Case B
|
--- 5 automated test steps
+
+Update Oct. 2013, L. Czub
+Integrates v0.4.5 changes for optional arguments and response error handling
+The v0.4.0 method calls are still visible as comments (look for CHANGE v0.4.5)
+So this file helps to understand where existing own code needs adjustment.
+
+Update Dec. 2013, L. Czub - examples for v0.4.6 api extensions added
+Update Jan. 2014, L. Czub - examples for v0.4.7 api and service extensions added
+
"""
-from testlink import TestlinkAPIClient, TestLinkHelper
-import sys
+from __future__ import print_function
+from testlink import TestlinkAPIClient, TestLinkHelper, TestGenReporter
+from testlink.testlinkerrors import TLResponseError
+import sys, os.path
+from platform import python_version
# precondition a)
-# SERVEUR_URL and KEY are defined in environment
+# SERVER_URL and KEY are defined in environment
# TESTLINK_API_PYTHON_SERVER_URL=http://YOURSERVER/testlink/lib/api/xmlrpc.php
# TESTLINK_API_PYTHON_DEVKEY=7ec252ab966ce88fd92c25d08635672b
#
@@ -58,134 +70,767 @@
# SERVEUR_URL and KEY are defined as command line arguments
# python TestLinkExample.py --server_url http://YOURSERVER/testlink/lib/api/xmlrpc.php
# --devKey 7ec252ab966ce88fd92c25d08635672b
+#
+# ATTENTION: With TestLink 1.9.7, cause of the new REST API, the SERVER_URL
+# has changed from
+# (old) http://YOURSERVER/testlink/lib/api/xmlrpc.php
+# to
+# (new) http://YOURSERVER/testlink/lib/api/xmlrpc/v1/xmlrpc.php
tl_helper = TestLinkHelper()
tl_helper.setParamsFromArgs('''Shows how to use the TestLinkAPI.
=> Counts and lists the Projects
=> Create a new Project with the following structure:''')
myTestLink = tl_helper.connect(TestlinkAPIClient)
+myPyVersion = python_version()
+myPyVersionShort = myPyVersion.replace('.', '')[:2]
-NEWPROJECT="NEW_PROJECT_API"
-NEWTESTPLAN="TestPlan_API"
+NEWTESTPLAN_A="TestPlan_API A"
+NEWTESTPLAN_B="TestPlan_API B"
+NEWTESTPLAN_C="TestPlan_API C - DeleteTest"
+NEWTESTPLAN_G="TestPlan_API G - generated"
+NEWPLATFORM_A='Big Birds %s' % myPyVersionShort
+NEWPLATFORM_B='Small Birds'
+NEWPLATFORM_C='Ugly Birds'
+NEWPLATFORM_G='generated Birds'
NEWTESTSUITE_A="A - First Level"
NEWTESTSUITE_B="B - First Level"
NEWTESTSUITE_AA="AA - Second Level"
NEWTESTCASE_AA="TESTCASE_AA"
NEWTESTCASE_B="TESTCASE_B"
+myApiVersion='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__)
+NEWBUILD_A='%s' % myApiVersion
+NEWBUILD_B='%s' % myApiVersion
+NEWBUILD_C='%s - DeleteTest' % myApiVersion
+NEWBUILD_D='%s - copyTestersTest' % myApiVersion
+NEWBUILD_G='%s - generated' % myApiVersion
+
+this_file_dirname=os.path.dirname(__file__)
+NEWATTACHMENT_PY= os.path.join(this_file_dirname, 'TestLinkExample.py')
+NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png')
+
+# Servers TestLink Version
+myTLVersion = myTestLink.testLinkVersion()
+myTLVersionShort = myTLVersion.replace('.', '')
+
+NEWPROJECT="NEW_PROJECT_API-%s" % myPyVersionShort
+NEWPREFIX="NPROAPI%s" % myPyVersionShort
+ITSNAME="myITS"
+
+# used connection settings
+print(myTestLink.connectionInfo())
+print("")
+
+# ensure tester and expert users exists
+myTestUserName="myTester"
+myTestUser1_ID=myTestLink.ensureUserExist(myTestUserName,
+ firstname="myFirstName", lastname="myLastName", mail="myTester@example.com")
+print("ensureUserExist", myTestUserName, myTestUser1_ID)
+
+myTestUserName2="myExpert"
+myTestUser2_ID=myTestLink.ensureUserExist(myTestUserName2)
+print("checkUser", myTestUserName2, myTestUser2_ID)
+
+# get user information
+response = myTestLink.getUserByLogin(myTestUserName)
+print("getUserByLogin", response)
+myTestUserID=response[0]['dbID']
+response = myTestLink.getUserByID(myTestUserID)
+print("getUserByID ", response)
+
+# example asking the api client about methods arguments
+print(myTestLink.whatArgs('assignTestCaseExecutionTask'))
-if myTestLink.checkDevKey() != True:
- print "Error with the devKey."
- sys.exit(-1)
-print "Number of Projects in TestLink: %s " % (myTestLink.countProjects(),)
-print ""
+# example handling Response Error Codes
+# first check an invalid devKey and than the own one
+try:
+ myTestLink.checkDevKey(devKey='007')
+except TLResponseError as tl_err:
+ if tl_err.code == 2000:
+ # expected invalid devKey Error
+ # now check the own one - just call with default settings
+ myTestLink.checkDevKey()
+ else:
+ # seems to be another response failure - we forward it
+ raise
+
+print("Number of Projects in TestLink: %s " % (myTestLink.countProjects()))
+print("")
myTestLink.listProjects()
-print ""
+print("")
+
+# Delete the project, if it already exists
+try:
+ response = myTestLink.deleteTestProject(NEWPREFIX)
+ print("deleteTestProject", response)
+except TLResponseError:
+ print("No project with prefix %s exists" % NEWPREFIX)
+
+# # get IssueTrackerSystem
+# aITS=myTestLink.getIssueTrackerSystem(aITSNAME)
+# print("getIssueTrackerSystem", aITS)
# Creates the project
-newProject = myTestLink.createTestProject(NEWPROJECT, "NPROAPI",
-"notes=This is a Project created with the API", "active=1", "public=1",
-"options=requirementsEnabled:0,testPriorityEnabled:1,automationEnabled:1,inventoryEnabled:0")
-isOk = newProject[0]['message']
-if isOk=="Success!":
- newProjectID = newProject[0]['id']
- print "New Project '%s' - id: %s" % (NEWPROJECT,newProjectID)
-else:
- print "Error creating the project '%s': %s " % (NEWPROJECT,isOk)
- sys.exit(-1)
+projInfo = 'Example created with Python %s API class %s in TL %s' % \
+ ( python_version(), myApiVersion, myTLVersion )
+newProject = myTestLink.createTestProject(NEWPROJECT, NEWPREFIX,
+ notes=projInfo, active=1, public=1,
+# itsname=ITSNAME, itsenabled=1,
+ options={'requirementsEnabled' : 0, 'testPriorityEnabled' : 1,
+ 'automationEnabled' : 1, 'inventoryEnabled' : 0})
+print("createTestProject", newProject)
+newProjectID = newProject[0]['id']
+print("New Project '%s' - id: %s" % (NEWPROJECT,newProjectID))
+
+# assign project roles to user 1 and get user information
+response = myTestLink.ensureUserExistWithProjectRole(myTestUserName, "tester", NEWPROJECT)
+print("ensureUserExistWithProjectRole user1 role tester", response)
+
+# assign project roles to user 2 and get user information
+response = myTestLink.ensureUserExistWithProjectRole(myTestUserName2, "senior tester", NEWPROJECT)
+print("ensureUserExistWithProjectRole user2 role senior tester", response)
+response = myTestLink.ensureUserExistWithProjectRole(myTestUserName2, "test designer", NEWPROJECT)
+print("ensureUserExistWithProjectRole user2 role test designer", response)
# Creates the test plan
-newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN, NEWPROJECT,
- "notes=New TestPlan created with the API","active=1", "public=1")
-isOk = newTestPlan[0]['message']
-if isOk=="Success!":
- newTestPlanID = newTestPlan[0]['id']
- print "New Test Plan '%s' - id: %s" % (NEWTESTPLAN,newTestPlanID)
-else:
- print "Error creating the Test Plan '%s': %s " % (NEWTESTPLAN, isOk)
- sys.exit(-1)
+newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_A, testprojectname=NEWPROJECT,
+ notes='New TestPlan created with the API',active=1, public=1)
+print("createTestPlan", newTestPlan)
+newTestPlanID_A = newTestPlan[0]['id']
+print("New Test Plan '%s' - id: %s" % (NEWTESTPLAN_A,newTestPlanID_A))
+
+# Create test plan B - uses no platforms
+newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_B, prefix=NEWPREFIX,
+ notes='New TestPlan created with the Generic API - uses no platforms.',
+ active=1, public=1)
+print("createTestPlan", newTestPlan)
+newTestPlanID_B = newTestPlan[0]['id']
+print("New Test Plan '%s' - id: %s" % (NEWTESTPLAN_B,newTestPlanID_B))
+
+# Create platform 'Big Birds x'
+newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_A,
+ notes='Platform for Big Birds, unique name, only used in this project',
+ platformondesign=True, platformonexecution=True)
+print("createPlatform", newPlatForm)
+newPlatFormID_A = newPlatForm['id']
+# Add Platform 'Big Bird x' to platform
+response = myTestLink.addPlatformToTestPlan(newTestPlanID_A, NEWPLATFORM_A)
+print("addPlatformToTestPlan", response)
+
+# Create platform 'Small Birds'
+newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_B,
+ notes='Platform for Small Birds, name used in all example projects',
+ platformondesign=True, platformonexecution=True)
+print("createPlatform", newPlatForm)
+newPlatFormID_B = newPlatForm['id']
+# Add Platform 'Small Bird' to platform
+response = myTestLink.addPlatformToTestPlan(newTestPlanID_A, NEWPLATFORM_B)
+print("addPlatformToTestPlan", response)
+
+# Create platform 'Ugly Birds'
+newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_C,
+ notes='Platform for Ugly Birds, will be removed from test plan',
+ platformondesign=True, platformonexecution=True)
+print("createPlatform", newPlatForm)
+newPlatFormID_C = newPlatForm['id']
+# Add Platform 'Ugly Bird' to platform
+response = myTestLink.addPlatformToTestPlan(newTestPlanID_A, NEWPLATFORM_C)
+print("addPlatformToTestPlan", response)
#Creates the test Suite A
newTestSuite = myTestLink.createTestSuite(newProjectID, NEWTESTSUITE_A,
"Details of the Test Suite A")
-isOk = newTestSuite[0]['message']
-if isOk=="ok":
- newTestSuiteID = newTestSuite[0]['id']
- print "New Test Suite '%s' - id: %s" % (NEWTESTSUITE_A, newTestSuiteID)
-else:
- print "Error creating the Test Suite '%s': %s " % (NEWTESTSUITE_A, isOk)
- sys.exit(-1)
-
-FirstLevelID = newTestSuiteID
+print("createTestSuite", newTestSuite)
+newTestSuiteID_A = newTestSuite[0]['id']
+print("New Test Suite '%s' - id: %s" % (NEWTESTSUITE_A, newTestSuiteID_A))
+
+FirstLevelID = newTestSuiteID_A
#Creates the test Suite B
newTestSuite = myTestLink.createTestSuite(newProjectID, NEWTESTSUITE_B,
"Details of the Test Suite B")
-isOk = newTestSuite[0]['message']
-if isOk=="ok":
- TestSuiteID_B = newTestSuite[0]['id']
- print "New Test Suite '%s' - id: %s" % (NEWTESTSUITE_B, TestSuiteID_B)
-else:
- print "Error creating the Test Suite '%s': %s " % (NEWTESTSUITE_B, isOk)
- sys.exit(-1)
+print("createTestSuite", newTestSuite)
+newTestSuiteID_B = newTestSuite[0]['id']
+print("New Test Suite '%s' - id: %s" % (NEWTESTSUITE_B, newTestSuiteID_B))
#Creates the test Suite AA
newTestSuite = myTestLink.createTestSuite(newProjectID, NEWTESTSUITE_AA,
- "Details of the Test Suite AA","parentid="+FirstLevelID)
-isOk = newTestSuite[0]['message']
-if isOk=="ok":
- TestSuiteID_AA = newTestSuite[0]['id']
- print "New Test Suite '%s' - id: %s" % (NEWTESTSUITE_AA, TestSuiteID_AA)
-else:
- print "Error creating the Test Suite '%s': %s " % (NEWTESTSUITE_AA, isOk)
- sys.exit(-1)
+ "Details of the Test Suite AA",parentid=FirstLevelID)
+print("createTestSuite", newTestSuite)
+newTestSuiteID_AA = newTestSuite[0]['id']
+print("New Test Suite '%s' - id: %s" % (NEWTESTSUITE_AA, newTestSuiteID_AA))
MANUAL = 1
AUTOMATED = 2
+READFORREVIEW=2
+REWORK=4
+HIGH=3
+MEDIUM=2
+LOW=1
-#Creates the test case TC_AA
+#Creates the test case TC_AA with state ready for review
myTestLink.initStep("Step action 1", "Step result 1", MANUAL)
myTestLink.appendStep("Step action 2", "Step result 2", MANUAL)
myTestLink.appendStep("Step action 3", "Step result 3", MANUAL)
myTestLink.appendStep("Step action 4", "Step result 4", MANUAL)
myTestLink.appendStep("Step action 5", "Step result 5", MANUAL)
+myTestLink.appendStep("Dummy step for delete tests",
+ "should be delete with deleteTestCaseSteps", MANUAL)
-newTestCase = myTestLink.createTestCase(NEWTESTCASE_AA, TestSuiteID_AA,
- newProjectID, "admin", "This is the summary of the Test Case AA",
- "preconditions=these are the preconditions")
-isOk = newTestCase[0]['message']
-if isOk=="Success!":
- newTestCaseID = newTestCase[0]['id']
- print "New Test Case '%s' - id: %s" % (NEWTESTCASE_AA, newTestCaseID)
-else:
- print "Error creating the Test Case '%s': %s " % (NEWTESTCASE_AA, isOk)
- sys.exit(-1)
-
-#Creates the test case TC_B
+newTestCase = myTestLink.createTestCase(NEWTESTCASE_AA, newTestSuiteID_AA,
+ newProjectID, myTestUserName, "This is the summary of the Test Case AA",
+ preconditions='these are the preconditions',
+ importance=LOW, state=READFORREVIEW, estimatedexecduration=10.1)
+print("createTestCase", newTestCase)
+newTestCaseID_AA = newTestCase[0]['id']
+print("New Test Case '%s' - id: %s" % (NEWTESTCASE_AA, newTestCaseID_AA))
+
+#Creates the test case TC_B with state rework - in wrong test suite A
myTestLink.initStep("Step action 1", "Step result 1", AUTOMATED)
myTestLink.appendStep("Step action 2", "Step result 2", AUTOMATED)
myTestLink.appendStep("Step action 3", "Step result 3", AUTOMATED)
myTestLink.appendStep("Step action 4", "Step result 4", AUTOMATED)
myTestLink.appendStep("Step action 5", "Step result 5", AUTOMATED)
-newTestCase = myTestLink.createTestCase(NEWTESTCASE_B, TestSuiteID_B,
- newProjectID, "admin", "This is the summary of the Test Case B",
- "preconditions=these are the preconditions",
- "executiontype=%i" % AUTOMATED)
-isOk = newTestCase[0]['message']
-if isOk=="Success!":
- newTestCaseID = newTestCase[0]['id']
- print "New Test Case '%s' - id: %s" % (NEWTESTCASE_B, newTestCaseID)
-else:
- print "Error creating the Test Case '%s': %s " % (NEWTESTCASE_B, isOk)
- sys.exit(-1)
-
-print ""
-print "Number of Projects in TestLink: %s " % (myTestLink.countProjects(),)
-print ""
-myTestLink.listProjects()
+newTestCase = myTestLink.createTestCase(NEWTESTCASE_B, newTestSuiteID_A,
+ newProjectID, myTestUserName, "This is the summary of the Test Case B",
+ preconditions='these are the preconditions', executiontype=AUTOMATED,
+ status=REWORK, estimatedexecduration=0.5)
+print("createTestCase TC-B in TS-A", newTestCase)
+newTestCaseID_B = newTestCase[0]['id']
+print("New Test Case '%s' - id: %s" % (NEWTESTCASE_B, newTestCaseID_B))
+
+# Move test case TC_B to correct test suite B
+tc_b_full_ext_id = myTestLink.getTestCase(newTestCaseID_B)[0]['full_tc_external_id']
+response = myTestLink.setTestCaseTestSuite(tc_b_full_ext_id, newTestSuiteID_B)
+print("setTestCaseTestSuite TC-B to TS-B" , response)
+
+# Add test cases to test plan - we need the full external id !
+# for every test case version 1 is used
+tc_version=1
+# TC AA should be tested with platforms 'Big Birds'+'Small Birds'
+tc_aa_full_ext_id = myTestLink.getTestCase(newTestCaseID_AA)[0]['full_tc_external_id']
+response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_A,
+ tc_aa_full_ext_id, tc_version, platformid=newPlatFormID_A)
+print("addTestCaseToTestPlan", response)
+tc_aa_full_ext_id = myTestLink.getTestCase(newTestCaseID_AA)[0]['full_tc_external_id']
+response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_A,
+ tc_aa_full_ext_id, tc_version, platformid=newPlatFormID_B)
+print("addTestCaseToTestPlan", response)
+# change test case TC_AA - delete step 6 (step 7 does not exist)
+response = myTestLink.deleteTestCaseSteps(tc_aa_full_ext_id, [7,6],
+ version=tc_version)
+print("deleteTestCaseSteps", response)
+
+# TC B should be tested with platform 'Small Birds'
+tc_b_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0]['full_tc_external_id']
+response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_A,
+ tc_b_full_ext_id, tc_version, platformid=newPlatFormID_B)
+print("addTestCaseToTestPlan", response)
+
+#Update test case TC_B -> high, change step 5, new step 6
+steps_tc_b = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0]['steps']
+steps_tc_b_v1u = steps_tc_b[:4]
+steps_tc_b_v1u.append(
+ {'step_number' : 5, 'actions' : "Step action 5 -b changed by updateTestCase" ,
+ 'expected_results' : "Step result 5 - b changed", 'execution_type' : AUTOMATED})
+steps_tc_b_v1u.append(
+ {'step_number' : 6, 'actions' : "Step action 6 -b added by updateTestCase" ,
+ 'expected_results' : "Step result 6 - b added", 'execution_type' : AUTOMATED})
+response = myTestLink.updateTestCase(tc_b_full_ext_id, version=tc_version,
+ steps=steps_tc_b_v1u, importance=MEDIUM, estimatedexecduration=3)
+print("updateTestCase", response)
+
+# create additional steps via createTestCaseSteps - action create
+steps_tc_b_c67 = [
+ {'step_number' : 6, 'actions' : "action 6 createTestCaseSteps.create" ,
+ 'expected_results' : "skip - cause step 6 already exist", 'execution_type' : AUTOMATED},
+ {'step_number' : 7, 'actions' : "action 7 createTestCaseSteps.create" ,
+ 'expected_results' : "create - cause step 7 not yet exist", 'execution_type' : AUTOMATED}]
+response = myTestLink.createTestCaseSteps('create', steps_tc_b_c67,
+ testcaseexternalid=tc_b_full_ext_id, version=tc_version)
+print("createTestCaseSteps.create", response)
+# create additional steps via createTestCaseSteps - action update
+steps_tc_b_c38 = [
+ {'step_number' : 3, 'actions' : "action 3 createTestCaseSteps.update" ,
+ 'expected_results' : "update - cause step 3 already exist", 'execution_type' : AUTOMATED},
+ {'step_number' : 8, 'actions' : "action 8 createTestCaseSteps.update" ,
+ 'expected_results' : "create - cause step 8 not yet exist", 'execution_type' : AUTOMATED}]
+response = myTestLink.createTestCaseSteps('update', steps_tc_b_c38,
+ testcaseid=newTestCaseID_B, version=tc_version)
+print("createTestCaseSteps.update", response)
+
+
+# In test plan B TC B should be tested without platform
+response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_B,
+ tc_b_full_ext_id, tc_version)
+print("addTestCaseToTestPlan", response)
+
+# # Try to Remove Platform 'Big Birds' from platform
+# response = myTestLink.removePlatformFromTestPlan(newTestPlanID_A, NEWPLATFORM_C)
+# print "removePlatformFromTestPlan", response
+
+# Remove Platform 'Ugly Birds' from platform
+response = myTestLink.removePlatformFromTestPlan(newTestPlanID_A, NEWPLATFORM_C)
+print("removePlatformFromTestPlan", response)
+
+# -- Create Build for TestPlan A (uses platforms)
+newBuild = myTestLink.createBuild(newTestPlanID_A, NEWBUILD_A,
+ 'Notes for the Build', releasedate="2016-12-31")
+print("createBuild", newBuild)
+newBuildID_A = newBuild[0]['id']
+print("New Build '%s' - id: %s" % (NEWBUILD_A, newBuildID_A))
+
+# assign user to test case execution tasks - test plan with platforms
+response = myTestLink.assignTestCaseExecutionTask( myTestUserName,
+ newTestPlanID_A, tc_aa_full_ext_id,
+ buildid=newBuildID_A, platformname=NEWPLATFORM_A)
+print("assignTestCaseExecutionTask", response)
+response = myTestLink.assignTestCaseExecutionTask( myTestUserName2,
+ newTestPlanID_A, tc_aa_full_ext_id,
+ buildname=NEWBUILD_A, platformid=newPlatFormID_B)
+print("assignTestCaseExecutionTask", response)
+response = myTestLink.assignTestCaseExecutionTask( myTestUserName,
+ newTestPlanID_A, tc_b_full_ext_id,
+ buildname=NEWBUILD_A, platformname=NEWPLATFORM_B)
+print("assignTestCaseExecutionTask", response)
+
+# get test case assigned tester
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_A, tc_aa_full_ext_id,
+ buildid=newBuildID_A, platformname=NEWPLATFORM_A)
+print("getTestCaseAssignedTester TC_AA TP_A Platform A", response)
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_A, tc_aa_full_ext_id,
+ buildname=NEWBUILD_A, platformid=newPlatFormID_B)
+print("getTestCaseAssignedTester TC_AA TP_A Platform B", response)
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_A, tc_b_full_ext_id,
+ buildname=NEWBUILD_A, platformname=NEWPLATFORM_B)
+print("getTestCaseAssignedTester TC_B TP_A Platform B", response)
+
+# get bugs for test case TC_AA in test plan A - state TC not executed
+response = myTestLink.getTestCaseBugs(newTestPlanID_A,
+ testcaseexternalid=tc_aa_full_ext_id)
+print("getTestCaseBugs TC_AA in TP_A (TC is not executed)", response)
+
+# report Test Case Results for platform 'Big Bird' with step results
+# TC_AA failed, build should be guessed, TC identified with external id
+newResult = myTestLink.reportTCResult(None, newTestPlanID_A, None, 'f', '', guess=True,
+ testcaseexternalid=tc_aa_full_ext_id,
+ platformname=NEWPLATFORM_A,
+ execduration=3.9, timestamp='2015-09-18 14:33',
+ steps=[{'step_number' : 3, 'result' : 'p', 'notes' : 'result note for passed step 3'},
+ {'step_number' : 4, 'result' : 'f', 'notes' : 'result note for failed step 4'}] )
+print("reportTCResult", newResult)
+newResultID_AA = newResult[0]['id']
+
+# get bugs for test case TC_AA in test plan A - state TC is executed
+response = myTestLink.getTestCaseBugs(newTestPlanID_A,
+ testcaseexternalid=tc_aa_full_ext_id)
+print("getTestCaseBugs TC_AA in TP_A (TC is executed, no bug)", response)
+
+# report Test Case Results for platform 'Small Bird'
+# TC_AA passed, build should be guessed, TC identified with external id
+newResult = myTestLink.reportTCResult(None, newTestPlanID_A, None, 'p', '', guess=True,
+ testcaseexternalid=tc_aa_full_ext_id,
+ platformname=NEWPLATFORM_B,
+ execduration='2.2', timestamp='2015-09-19 14:33:02')
+print("reportTCResult", newResult)
+newResultID_AA_p = newResult[0]['id']
+# TC_B passed, explicit build and some notes , TC identified with internal id
+newResult = myTestLink.reportTCResult(newTestCaseID_B, newTestPlanID_A, NEWBUILD_A,
+ 'p', 'first try', platformname=NEWPLATFORM_B)
+print("reportTCResult", newResult)
+newResultID_B = newResult[0]['id']
+
+#FIXME: know 1.9.20_fixed issue
+# testlink.testlinkerrors.TLResponseError: 6002: (uploadExecutionAttachment) - Error inserting attachment on DB
+#
+# E_WARNING base64_decode() expects parameter 1 to be string, array given
+# - in /var/www/html/lib/api/xmlrpc/v1/xmlrpc.class.php - Line 5834
+#
+# # add this (text) file as Attachemnt to last execution of TC_B with
+# # different filename 'MyPyExampleApiClient.py'
+# a_file=open(NEWATTACHMENT_PY)
+# newAttachment = myTestLink.uploadExecutionAttachment(a_file, newResultID_B,
+# 'Textfile Example', 'Text Attachment Example for a TestCase Execution',
+# filename='MyPyExampleApiClient.py')
+# print("uploadExecutionAttachment", newAttachment)
+# # add png file as Attachemnt to last execution of TC_AA
+# # !Attention - on WINDOWS use binary mode for none text file
+# # see http://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files
+# a_file=open(NEWATTACHMENT_PNG, mode='rb')
+# newAttachment = myTestLink.uploadExecutionAttachment(a_file, newResultID_AA,
+# 'PNG Example', 'PNG Attachment Example for a TestCase Execution')
+# print("uploadExecutionAttachment", newAttachment)
+
+# -- Create Build for TestPlan B (uses no platforms)
+newBuild = myTestLink.createBuild(newTestPlanID_B, NEWBUILD_B,
+ 'Build for TestPlan without platforms', releasedate='2016-11-30')
+print("createBuild", newBuild)
+newBuildID_B = newBuild[0]['id']
+print("New Build '%s' - id: %s" % (NEWBUILD_B, newBuildID_B))
+
+# assign user to test case execution tasks - test plans without platforms
+response = myTestLink.assignTestCaseExecutionTask( myTestUserName,
+ newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B)
+print("assignTestCaseExecutionTask", response)
+# get test case assigned tester
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B)
+print("getTestCaseAssignedTester TC_B TP_B no Platform", response)
+
+# try to remove not assigned tester
+response = myTestLink.unassignTestCaseExecutionTask(
+ newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B,
+ user=myTestUserName2)
+print("unassignTestCaseExecutionTask not assigned user", response)
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B)
+print("getTestCaseAssignedTester TC_B TP_B no Platform", response)
+
+# try to remove all assigned tester
+response = myTestLink.unassignTestCaseExecutionTask(
+ newTestPlanID_B, tc_b_full_ext_id, buildid=newBuildID_B,
+ action='unassignAll')
+print("unassignTestCaseExecutionTask unassignAll", response)
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B)
+print("getTestCaseAssignedTester TC_B TP_B no Platform", response)
+
+# reassign user to test case execution tasks - test plans without platforms
+response = myTestLink.assignTestCaseExecutionTask( myTestUserName,
+ newTestPlanID_B, tc_b_full_ext_id, buildid=newBuildID_B)
+print("assignTestCaseExecutionTask", response)
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B)
+print("getTestCaseAssignedTester TC_B TP_B no Platform", response)
+
+
+# TC_B blocked (without platform), explicit build and some notes ,
+# TC identified with internal id, report by myTestUserName
+newResult = myTestLink.reportTCResult(newTestCaseID_B, newTestPlanID_B, NEWBUILD_B,
+ 'f', "no birds are singing", bugid='007',
+ user=myTestUserName)
+print("reportTCResult", newResult)
+newResultID_B_f = newResult[0]['id']
+newResult = myTestLink.reportTCResult(newTestCaseID_B, newTestPlanID_B, NEWBUILD_B,
+ 'b', "hungry birds blocks the execution",
+ bugid='008', user=myTestUserName)
+print("reportTCResult", newResult)
+newResultID_B_b = newResult[0]['id']
+# get bugs for test case TC_B in test plan B - state TC is executed with bug
+response = myTestLink.getTestCaseBugs(newTestPlanID_B,
+ testcaseid=newTestCaseID_B)
+print("getTestCaseBugs TC_B in TP_B (TC is executed with 2 bugs)", response)
+
+# now we make a mistake and commit the same result a second time
+# and try to delete this mistake
+newResult = myTestLink.reportTCResult(newTestCaseID_B, newTestPlanID_B, NEWBUILD_B,
+ 'b', "mistake, commit same result a second time")
+print("reportTCResult", newResult)
+newResultID_B_b2 = int(newResult[0]['id'])
+try:
+ # if TL configuration allows deletion of executions, no error will occur
+ response = myTestLink.deleteExecution(newResultID_B_b2)
+ print("deleteExecution", response)
+except TLResponseError as tl_err:
+ if tl_err.code == 232:
+ # TL configuration does not allow deletion of executions
+ pass
+ else:
+ # sh..: another problem occurs
+ raise
+
+# now we try to change the execution types of the test cases
+# - AA from manual -> auto and B from auto -> manual
+newResult = myTestLink.setTestCaseExecutionType(tc_aa_full_ext_id, tc_version,
+ newProjectID, AUTOMATED)
+print("setTestCaseExecutionType", response)
+newResult = myTestLink.setTestCaseExecutionType(tc_b_full_ext_id, tc_version,
+ newProjectID, MANUAL)
+print("setTestCaseExecutionType", response)
+
+# create TestPlan C with Platform, Build , TestCase, assigned TestCase
+# and delete it
+newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_C, NEWPROJECT,
+ notes='TestPlan for delete test.',
+ active=1, public=1)
+print("createTestPlan for DeleteTest", newTestPlan)
+newTestPlanID_C = newTestPlan[0]['id']
+print("Test Plan '%s' - id: %s" % (NEWTESTPLAN_C,newTestPlanID_C))
+newBuild = myTestLink.createBuild(newTestPlanID_C, NEWBUILD_C,
+ 'Build for TestPlan delete test')
+print("createBuild for DeleteTest", newBuild)
+newBuildID_C = newBuild[0]['id']
+print("Build '%s' - id: %s" % (NEWBUILD_C, newBuildID_C))
+response = myTestLink.addPlatformToTestPlan(newTestPlanID_C, NEWPLATFORM_C)
+print("addPlatformToTestPlan", response)
+response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_C,
+ tc_aa_full_ext_id, tc_version, platformid=newPlatFormID_C)
+print("addTestCaseToTestPlan", response)
+response = myTestLink.assignTestCaseExecutionTask( myTestUserName,
+ newTestPlanID_C, tc_aa_full_ext_id, buildid=newBuildID_C,
+ platformid=newPlatFormID_C)
+print("assignTestCaseExecutionTask", response)
+newResult = myTestLink.reportTCResult(newTestCaseID_AA, newTestPlanID_C,
+ NEWBUILD_C, 'p', "TP delete test",
+ platformname=NEWPLATFORM_C)
+print("reportTCResult", newResult)
+newResultID_B = newResult[0]['id']
+
+#FIXME: know 1.9.20_fixed issue
+# testlink.testlinkerrors.TLResponseError: 6002: (uploadExecutionAttachment) - Error inserting attachment on DB
+#
+# E_WARNING base64_decode() expects parameter 1 to be string, array given
+# - in /var/www/html/lib/api/xmlrpc/v1/xmlrpc.class.php - Line 5834
+#
+# newAttachment = myTestLink.uploadExecutionAttachment(NEWATTACHMENT_PY, newResultID_B,
+# 'Textfile Example', 'Attachment Example for a TC Execution and TP delete test',
+# filename='MyPyTPDeleteTest.py')
+# print("uploadExecutionAttachment", newAttachment)
+response = myTestLink.getTotalsForTestPlan(newTestPlanID_C)
+print("getTotalsForTestPlan before delete", response)
+response = myTestLink.deleteTestPlan(newTestPlanID_C)
+print("deleteTestPlan", response)
+try:
+ response = myTestLink.getTotalsForTestPlan(newTestPlanID_C)
+ print("getTotalsForTestPlan after delete", response)
+except TLResponseError as tl_err:
+ print(tl_err.message)
+
+# -- Create Build D and copy Testers from Build A
+newBuild = myTestLink.createBuild(newTestPlanID_A, NEWBUILD_D,
+ 'Build with copied testers from Build ' + NEWBUILD_A,
+ active=1, open=1, copytestersfrombuild=newBuildID_A)
+print("createBuild", newBuild)
+newBuildID_D = newBuild[0]['id']
+print("New Build '%s' - id: %s" % (NEWBUILD_D, newBuildID_D))
+
+# close build A - buildid must be converted to an integer
+response = myTestLink.closeBuild( int(newBuildID_A) )
+print("closeBuild", response)
+
+# get information - TestProject
+response = myTestLink.getTestProjectByName(NEWPROJECT)
+print("getTestProjectByName", response)
+response = myTestLink.getProjectTestPlans(newProjectID)
+print("getProjectTestPlans", response)
+response = myTestLink.getFirstLevelTestSuitesForTestProject(newProjectID)
+print("getFirstLevelTestSuitesForTestProject", response)
+response = myTestLink.getProjectPlatforms(newProjectID)
+print("getProjectPlatforms", response)
+response = myTestLink.getProjectKeywords(newProjectID)
+print("getProjectKeywords", response)
+
+# get information - testPlan
+response = myTestLink.getTestPlanByName(NEWPROJECT, NEWTESTPLAN_A)
+print("getTestPlanByName", response)
+response = myTestLink.getTotalsForTestPlan(newTestPlanID_A)
+print("getTotalsForTestPlan", response)
+response = myTestLink.getBuildsForTestPlan(newTestPlanID_A)
+print("getBuildsForTestPlan", response)
+response = myTestLink.getLatestBuildForTestPlan(newTestPlanID_A)
+print("getLatestBuildForTestPlan", response)
+response = myTestLink.getTestPlanPlatforms(newTestPlanID_A)
+print("getTestPlanPlatforms", response)
+response = myTestLink.getTestSuitesForTestPlan(newTestPlanID_A)
+print("getTestSuitesForTestPlan", response)
+# get failed Testcases
+# -- Start CHANGE v0.4.5 --
+#response = myTestLink.getTestCasesForTestPlan(newTestPlanID_A, 'executestatus=f')
+response = myTestLink.getTestCasesForTestPlan(newTestPlanID_A, executestatus='f')
+# -- END CHANGE v0.4.5 --
+print("getTestCasesForTestPlan A failed ", response)
+# get Testcases for Plattform SmallBird
+response = myTestLink.getTestCasesForTestPlan(newTestPlanID_A, platformid=newPlatFormID_B)
+print("getTestCasesForTestPlan A SmallBirds", response)
+
+# get information - TestSuite
+response = myTestLink.getTestSuiteByID(newTestSuiteID_B)
+print("getTestSuiteByID", response)
+response = myTestLink.getTestSuitesForTestSuite(newTestSuiteID_A)
+print("getTestSuitesForTestSuite A", response)
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, True, 'full')
+print("getTestCasesForTestSuite A", response)
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, False, 'only_id')
+print("getTestCasesForTestSuite B", response)
+
+# Update test suite B details - Using Project ID
+updatedTestSuite = myTestLink.updateTestSuite(newTestSuiteID_B,
+ testprojectid=newProjectID,
+ details="updated Details of the Test Suite B")
+print("updateTestSuite", updatedTestSuite)
+
+# Update test suite A name and order details - Using Project Name
+# with TL 1.9.15 this step fails - solution see TL Mantis Ticket 7696
+#
+changedNEWTESTSUITE_A = NEWTESTSUITE_A + ' - Changed'
+updatedTestSuite = myTestLink.updateTestSuite(newTestSuiteID_A, prefix=NEWPREFIX,
+ testsuitename = changedNEWTESTSUITE_A, order=1)
+print("updateTestSuite", updatedTestSuite)
+
+# get all test suites, using the same name - test Suite B
+response = myTestLink.getTestSuite(NEWTESTSUITE_B, NEWPREFIX)
+print("getTestSuite", response)
+
+# get informationen - TestCase
+# -- Start CHANGE v0.4.5 --
+#response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, None, NEWPROJECT)
+response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, testprojectname=NEWPROJECT)
+# -- END CHANGE v0.4.5 --
+print("getTestCaseIDByName", response)
+tcpathname = '::'.join([NEWPROJECT, changedNEWTESTSUITE_A, NEWTESTSUITE_AA, NEWTESTCASE_AA])
+response = myTestLink.getTestCaseIDByName('unknown', testcasepathname=tcpathname)
+print("getTestCaseIDByName", response)
+# get execution result
+response = myTestLink.getLastExecutionResult(newTestPlanID_A, None,
+ testcaseexternalid=tc_aa_full_ext_id)
+print("getLastExecutionResult", response)
+response = myTestLink.getExecutionSet(newTestPlanID_A,
+ testcaseexternalid=tc_aa_full_ext_id)
+print("getExecutionSet", response)
+response = myTestLink.getLastExecutionResult(newTestPlanID_A, newTestCaseID_B)
+print("getLastExecutionResult", response)
+response = myTestLink.getExecutionSet(newTestPlanID_A,
+ testcaseid=newTestCaseID_B)
+print("getExecutionSet", response)
+response = myTestLink.getLastExecutionResult(newTestPlanID_A, newTestCaseID_AA,
+ platformid=newPlatFormID_A)
+print("getLastExecutionResult", response)
+response = myTestLink.getExecutionSet(newTestPlanID_A,
+ testcaseid=newTestCaseID_AA, platformid=newPlatFormID_A)
+print("getExecutionSet", response)
+
+response = myTestLink.getAllExecutionsResults(newTestPlanID_A,
+ testcaseid=newTestCaseID_AA, options={'getBugs' : 1})
+print("getAllExecutionsResults", response)
+response = myTestLink.getAllExecutionsResults(newTestPlanID_B,
+ testcaseid=newTestCaseID_B, options={'getBugs' : 1})
+print("getAllExecutionsResults", response)
+
+
+response = myTestLink.getExecCountersByBuild(newTestPlanID_A)
+print("getExecCountersByBuild", response)
+response = myTestLink.getExecCountersByBuild(newTestPlanID_B)
+print("getExecCountersByBuild", response)
+response = myTestLink.getTestCaseKeywords(testcaseexternalid=tc_b_full_ext_id)
+print("getTestCaseKeywords noKeyWords", response)
+
+# get information - general
+response = myTestLink.getFullPath(int(newTestSuiteID_AA))
+print("getFullPath", response)
+response = myTestLink.getFullPath([int(newTestCaseID_AA), int(newTestCaseID_B)])
+print("getFullPath", response)
+
+# attachments
+# add png file as Attachment to test project
+a_file=open(NEWATTACHMENT_PNG, mode='rb')
+newAttachment = myTestLink.uploadTestProjectAttachment(a_file, newProjectID,
+ title='PNG Example', description='PNG Attachment Example for a TestProject')
+print("uploadTestProjectAttachment", newAttachment)
+# add png file as Attachnent to test suite A - uploadXxzAttachmemt also file path
+newAttachment = myTestLink.uploadTestSuiteAttachment(NEWATTACHMENT_PNG, newTestSuiteID_A,
+ title='PNG Example', description='PNG Attachment Example for a TestSuite')
+print("uploadTestSuiteAttachment", newAttachment)
+# get Attachment of test suite A
+response = myTestLink.getTestSuiteAttachments(newTestSuiteID_A)
+print("getTestSuiteAttachments", response)
+
+# copy test case - as a new TC version
+print("create new version of TC B")
+response = myTestLink.copyTCnewVersion(newTestCaseID_B,
+ summary='new version of TC B', importance='1')
+print('copyTCnewVersion', response)
+
+# get the different TC versions
+response = myTestLink.getTestCaseByVersion(newTestCaseID_B, 1)
+print('getTestCaseByVersion v1', response)
+response = myTestLink.getTestCaseByVersion(newTestCaseID_B, 2)
+print('getTestCaseByVersion v2', response)
+response = myTestLink.getTestCaseByVersion(newTestCaseID_B)
+print('getTestCaseByVersion vNone', response)
+
+#FIXME: know 1.9.19 issue
+# E_WARNING base64_decode() expects parameter 1 to be string, array given
+# - in /var/www/html/lib/api/xmlrpc/v1/xmlrpc.class.php - Line 5103
+#
+# # add png file as Attachment to test case B version 1
+# a_file=open(NEWATTACHMENT_PNG, mode='rb')
+# newAttachment = myTestLink.uploadTestCaseAttachment(a_file, newTestCaseID_B, 1,
+# title='PNG Example v1', description='PNG Attachment Example for a TestCase v1')
+# print("uploadTestCaseAttachment v1", newAttachment)
+# # add py file as Attachment to test case B version 2
+# a_file=open(NEWATTACHMENT_PY, mode='rb')
+# newAttachment = myTestLink.uploadTestCaseAttachment(a_file, newTestCaseID_B, 2,
+# title='Textfile Example v2', description='Text Attachment Example for a TestCase v2')
+# print("uploadTestCaseAttachment v2", newAttachment)
+#
+# # get Attachment of test case B v1
+# # response = myTestLink.getTestCaseAttachments(testcaseexternalid=tc_aa_full_ext_id)
+# # print "getTestCaseAttachments", response
+# response = myTestLink.getTestCaseAttachments(testcaseid=newTestCaseID_B, version=1)
+# print("getTestCaseAttachments v1", response)
+#
+# # get Attachment of test case B - last version
+# # response = myTestLink.getTestCaseAttachments(testcaseexternalid=tc_aa_full_ext_id)
+# # print "getTestCaseAttachments", response
+# response = myTestLink.getTestCaseAttachments(testcaseid=newTestCaseID_B, version=2)
+# attachment_id = list(response)[0]
+# print("getTestCaseAttachments v2", response[attachment_id]['title'])
+# response = myTestLink.getTestCaseAttachments(testcaseid=newTestCaseID_B)
+# print("getTestCaseAttachments vNone", response[attachment_id]['name'])
+
+
+# copy test case - as new TC in a different TestSuite
+print("copy TC B as TC BA into Test suite A")
+response = myTestLink.copyTCnewTestCase(newTestCaseID_B,
+ testsuiteid=newTestSuiteID_A, testcasename='%sA' % NEWTESTCASE_B)
+print('copyTCnewTestCase', response)
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, False, 'simple')
+print('getTestCasesForTestSuite B', response)
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, True, 'simple')
+print('getTestCasesForTestSuite A', response)
+
+
+
+
+# sample, how the test plan can be updated to use the new tc version
+# site effect of this step, assigned testers and existing execution results are
+# not accessible anymore via the TL Web Gui.
+# That is the reason, why we have uncomment it for the normal sample execution
+# response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_B,
+# tc_b_full_ext_id, tc_version+1,
+# overwrite=1)
+# print("addTestCaseToTestPlan overwrite", response)
+
+# sample, how to use TestGenReporter for adding test case result for test plans,
+# test builds, which are not yet defined
+tgr = TestGenReporter(myTestLink, [tc_aa_full_ext_id, tc_b_full_ext_id],
+ testprojectname=NEWPROJECT,
+ testplanname=NEWTESTPLAN_G, platformname=NEWPLATFORM_G,
+ buildname=NEWBUILD_G, status='p')
+tgr.report()
+print("TestPlan, Build, Platform generated with reporting TC results", tgr )
+
+
+print("")
+print("Number of Projects in TestLink: %s " % myTestLink.countProjects())
+print("Number of Platforms (in TestPlans): %s " % myTestLink.countPlatforms())
+print("Number of Builds : %s " % myTestLink.countBuilds())
+print("Number of TestPlans : %s " % myTestLink.countTestPlans())
+print("Number of TestSuites : %s " % myTestLink.countTestSuites())
+print("Number of TestCases (in TestSuites): %s " % myTestLink.countTestCasesTS())
+print("Number of TestCases (in TestPlans) : %s " % myTestLink.countTestCasesTP())
+print("")
+
+print()
+print("")
+myTestLink.listProjects()
diff --git a/example/TestLinkExampleGenericApi.py b/example/TestLinkExampleGenericApi.py
new file mode 100644
index 0000000..1618ff2
--- /dev/null
+++ b/example/TestLinkExampleGenericApi.py
@@ -0,0 +1,808 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2013-2021 Luiko Czub, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+
+"""
+
+Shows how to use the TestLinkAPIGeneric.
+- does equal things as Example TestLinkAPI in TestLinkExample.py
+ - exception - this test project uses platforms
+
+=> Counts and lists the Projects
+=> Create a new Project with the following structure:
+
+
+NewProject
+ |
+ ----NewTestPlan
+ |
+ ------ Test Suite A
+ | |
+ | ------- Test Suite AA
+ | |
+ | --------- Test Case AA
+ | |
+ ------ Test Suite B --- 5 manual test steps
+ |
+ --------- Test Case B
+ |
+ --- 5 automated test steps
+"""
+from __future__ import print_function
+from testlink import TestlinkAPIGeneric, TestLinkHelper
+from testlink.testlinkerrors import TLResponseError
+import sys, os.path
+from platform import python_version
+
+# precondition a)
+# SERVER_URL and KEY are defined in environment
+# TESTLINK_API_PYTHON_SERVER_URL=http://YOURSERVER/testlink/lib/api/xmlrpc.php
+# TESTLINK_API_PYTHON_DEVKEY=7ec252ab966ce88fd92c25d08635672b
+#
+# alternative precondition b)
+# SERVEUR_URL and KEY are defined as command line arguments
+# python TestLinkExample.py --server_url http://YOURSERVER/testlink/lib/api/xmlrpc.php
+# --devKey 7ec252ab966ce88fd92c25d08635672b
+#
+# ATTENTION: With TestLink 1.9.7, cause of the new REST API, the SERVER_URL
+# has changed from
+# (old) http://YOURSERVER/testlink/lib/api/xmlrpc.php
+# to
+# (new) http://YOURSERVER/testlink/lib/api/xmlrpc/v1/xmlrpc.php
+tl_helper = TestLinkHelper()
+tl_helper.setParamsFromArgs('''Shows how to use the TestLinkAPI.
+=> Counts and lists the Projects
+=> Create a new Project with the following structure:''')
+myTestLink = tl_helper.connect(TestlinkAPIGeneric)
+
+myPyVersion = python_version()
+myPyVersionShort = myPyVersion.replace('.', '')[:2]
+
+NEWTESTPLAN_A="TestPlan_API_GENERIC A"
+NEWTESTPLAN_B="TestPlan_API_GENERIC B"
+NEWTESTPLAN_C="TestPlan_API_GENERIC C - DeleteTest"
+NEWPLATFORM_A='Big Bird %s' % myPyVersionShort
+NEWPLATFORM_B='Small Bird'
+NEWPLATFORM_C='Ugly Bird'
+NEWTESTSUITE_A="A - First Level"
+NEWTESTSUITE_B="B - First Level"
+NEWTESTSUITE_AA="AA - Second Level"
+NEWTESTCASE_AA="TESTCASE_AA"
+NEWTESTCASE_B="TESTCASE_B"
+myApiVersion='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__)
+NEWBUILD_A='%s' % myApiVersion
+NEWBUILD_B='%s' % myApiVersion
+NEWBUILD_C='%s - DeleteTest' % myApiVersion
+NEWBUILD_D='%s - copyTestersTest' % myApiVersion
+
+this_file_dirname=os.path.dirname(__file__)
+NEWATTACHMENT_PY= os.path.join(this_file_dirname, 'TestLinkExampleGenericApi.py')
+NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png')
+
+# Servers TestLink Version
+myTLVersion = myTestLink.testLinkVersion()
+myTLVersionShort = myTLVersion.replace('.', '')
+
+NEWPROJECT="PROJECT_API_GENERIC-%s" % myPyVersionShort
+NEWPREFIX="GPROAPI%s" % myPyVersionShort
+ITSNAME="myITS"
+
+# used connection settings
+print(myTestLink.connectionInfo())
+print("")
+
+def checkUser(name1, name2):
+ """ checks if user NAME1_NAME2 exists
+ when not , user will be created
+ returns username + userid
+ """
+
+ login = "{}_{}".format(name1, name2)
+ mail = "{}.{}@example.com".format(name1, name2)
+ try:
+ response = myTestLink.getUserByLogin(login)
+ userID = response[0]['dbID']
+ except TLResponseError as tl_err:
+ if tl_err.code == 10000:
+ # Cannot Find User Login - create new user
+ userID = myTestLink.createUser(login, name1, name2, mail)
+ else:
+ # seems to be another response failure - we forward it
+ raise
+
+ return login, userID
+
+# ensure tester and expert users exists
+myTestUserName, myTestUser1_ID=checkUser("myTester", "pyTLapi")
+print("checkUser", myTestUserName, myTestUser1_ID)
+myTestUserName2, myTestUser2_ID=checkUser("myExpert", "pyTLapi")
+print("checkUser", myTestUserName2, myTestUser2_ID)
+
+# get user information
+response = myTestLink.getUserByLogin(myTestUserName)
+print("getUserByLogin", response)
+myTestUserID=response[0]['dbID']
+response = myTestLink.getUserByID(myTestUserID)
+print("getUserByID ", response)
+
+# example asking the api client about methods arguments
+print(myTestLink.whatArgs('createTestCase'))
+
+# example handling Response Error Codes
+# first check an invalid devKey and than the own one
+try:
+ myTestLink.checkDevKey(devKey='007')
+except TLResponseError as tl_err:
+ if tl_err.code == 2000:
+ # expected invalid devKey Error
+ # now check the own one - just call with default settings
+ myTestLink.checkDevKey()
+ else:
+ # seems to be another response failure - we forward it
+ raise
+
+print("Number of Projects in TestLink: %i " % len(myTestLink.getProjects()))
+print("")
+for project in myTestLink.getProjects():
+ print("Name: %(name)s ID: %(id)s " % project)
+print("")
+
+# Delete the project, if it already exists
+try:
+ response = myTestLink.deleteTestProject(NEWPREFIX)
+ print("deleteTestProject", response)
+except TLResponseError:
+ print("No project with prefix %s exists" % NEWPREFIX)
+
+# # get IssueTrackerSystem
+# aITS=myTestLink.getIssueTrackerSystem(aITSNAME)
+# print("getIssueTrackerSystem", aITS)
+
+# Creates the project
+projInfo = 'Example created with Python %s API class %s in TL %s' % \
+ ( myPyVersion, myApiVersion, myTLVersion )
+newProject = myTestLink.createTestProject(NEWPROJECT, NEWPREFIX,
+ notes=projInfo, active=1, public=1,
+# itsname=ITSNAME, itsenabled=1,
+ options={'requirementsEnabled' : 1, 'testPriorityEnabled' : 1,
+ 'automationEnabled' : 1, 'inventoryEnabled' : 1})
+print("createTestProject", newProject)
+newProjectID = newProject[0]['id']
+print("New Project '%s' - id: %s" % (NEWPROJECT,newProjectID))
+
+# assign project roles to user 1 and get user information
+response = myTestLink.setUserRoleOnProject(myTestUser1_ID, "tester", newProjectID)
+print("setUserRoleOnProject user1 role tester", response)
+response = myTestLink.getUserByID(myTestUser1_ID)
+print("getUserByID user1", response)
+
+# assign project roles to user 2 and get user information
+response = myTestLink.setUserRoleOnProject(myTestUser2_ID, "senior tester", newProjectID)
+print("setUserRoleOnProject user2 role senior tester", response)
+response = myTestLink.getUserByID(myTestUser2_ID)
+print("getUserByID user2", response)
+response = myTestLink.setUserRoleOnProject(myTestUser2_ID, "test designer", newProjectID)
+print("setUserRoleOnProject user2 role test designer", response)
+response = myTestLink.getUserByID(myTestUser2_ID)
+print("getUserByID user2", response)
+
+
+# Create test plan A - uses platforms
+newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_A, testprojectname=NEWPROJECT,
+ notes='New TestPlan created with the Generic API - uses platforms.',
+ active=1, public=1)
+print("createTestPlan", newTestPlan)
+newTestPlanID_A = newTestPlan[0]['id']
+print("New Test Plan '%s' - id: %s" % (NEWTESTPLAN_A,newTestPlanID_A))
+
+# Create test plan B - uses no platforms
+newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_B, prefix=NEWPREFIX,
+ notes='New TestPlan created with the Generic API - uses no platforms.',
+ active=1, public=1)
+print("createTestPlan", newTestPlan)
+newTestPlanID_B = newTestPlan[0]['id']
+print("New Test Plan '%s' - id: %s" % (NEWTESTPLAN_B,newTestPlanID_B))
+
+# Create platform 'Big Bird x'
+newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_A,
+ notes='Platform for Big Birds, unique name, only used in this project',
+ platformondesign=True, platformonexecution=True)
+print("createPlatform", newPlatForm)
+newPlatFormID_A = newPlatForm['id']
+# Add Platform 'Big Bird x' to platform
+response = myTestLink.addPlatformToTestPlan(newTestPlanID_A, NEWPLATFORM_A)
+print("addPlatformToTestPlan", response)
+
+# Create platform 'Small Bird'
+newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_B,
+ notes='Platform for Small Birds, name used in all example projects',
+ platformondesign=True, platformonexecution=True)
+print("createPlatform", newPlatForm)
+newPlatFormID_B = newPlatForm['id']
+# Add Platform 'Small Bird' to platform
+response = myTestLink.addPlatformToTestPlan(newTestPlanID_A, NEWPLATFORM_B)
+print("addPlatformToTestPlan", response)
+
+# Create platform 'Ugly Bird'
+newPlatForm = myTestLink.createPlatform(NEWPROJECT, NEWPLATFORM_C,
+ notes='Platform for Ugly Birds, will be removed from test plan',
+ platformondesign=True, platformonexecution=True)
+print("createPlatform", newPlatForm)
+newPlatFormID_C = newPlatForm['id']
+# Add Platform 'Ugly Bird' to platform
+response = myTestLink.addPlatformToTestPlan(newTestPlanID_A, NEWPLATFORM_C)
+print("addPlatformToTestPlan", response)
+
+#Creates the test Suite A
+newTestSuite = myTestLink.createTestSuite(newProjectID, NEWTESTSUITE_A,
+ "Details of the Test Suite A")
+print("createTestSuite", newTestSuite)
+newTestSuiteID_A = newTestSuite[0]['id']
+print("New Test Suite '%s' - id: %s" % (NEWTESTSUITE_A, newTestSuiteID_A))
+
+FirstLevelID = newTestSuiteID_A
+
+#Creates the test Suite B
+newTestSuite = myTestLink.createTestSuite(newProjectID, NEWTESTSUITE_B,
+ "Details of the Test Suite B")
+print("createTestSuite", newTestSuite)
+newTestSuiteID_B = newTestSuite[0]['id']
+print("New Test Suite '%s' - id: %s" % (NEWTESTSUITE_B, newTestSuiteID_B))
+
+#Creates the test Suite AA
+newTestSuite = myTestLink.createTestSuite(newProjectID, NEWTESTSUITE_AA,
+ "Details of the Test Suite AA",parentid=FirstLevelID)
+print("createTestSuite", newTestSuite)
+newTestSuiteID_AA = newTestSuite[0]['id']
+print("New Test Suite '%s' - id: %s" % (NEWTESTSUITE_AA, newTestSuiteID_AA))
+
+MANUAL = 1
+AUTOMATED = 2
+READFORREVIEW=2
+REWORK=4
+HIGH=3
+MEDIUM=2
+LOW=1
+#
+# #Creates the test case TC_AA with state ready for review
+steps_tc_aa = [
+ {'step_number' : 1, 'actions' : "Step action 1 - aa" ,
+ 'expected_results' : "Step result 1 - aa", 'execution_type' : MANUAL},
+ {'step_number' : 2, 'actions' : "Step action 2 - aa" ,
+ 'expected_results' : "Step result 2 - aa", 'execution_type' : MANUAL},
+ {'step_number' : 3, 'actions' : "Step action 3 - aa" ,
+ 'expected_results' : "Step result 3 - aa", 'execution_type' : MANUAL},
+ {'step_number' : 4, 'actions' : "Step action 4 - aa" ,
+ 'expected_results' : "Step result 4 - aa", 'execution_type' : MANUAL},
+ {'step_number' : 5, 'actions' : "Step action 5 - aa" ,
+ 'expected_results' : "Step result 5 - aa", 'execution_type' : MANUAL},
+ {'step_number' : 8, 'actions' : "Dummy step for delete tests" ,
+ 'expected_results' : "should be delete with deleteTestCaseSteps",
+ 'execution_type' : MANUAL}
+ ]
+newTestCase = myTestLink.createTestCase(NEWTESTCASE_AA, newTestSuiteID_AA,
+ newProjectID, myTestUserName, "This is the summary of the Test Case AA",
+ steps_tc_aa, preconditions='these are the preconditions',
+ importance=LOW, state=READFORREVIEW, estimatedexecduration=10.1)
+print("createTestCase", newTestCase)
+newTestCaseID_AA = newTestCase[0]['id']
+print("New Test Case '%s' - id: %s" % (NEWTESTCASE_AA, newTestCaseID_AA))
+
+# Creates the test case TC_B with state rework - in wrong test suite A
+steps_tc_b = [
+ {'step_number' : 1, 'actions' : "Step action 1 -b " ,
+ 'expected_results' : "Step result 1 - b", 'execution_type' : AUTOMATED},
+ {'step_number' : 2, 'actions' : "Step action 2 -b " ,
+ 'expected_results' : "Step result 2 - b", 'execution_type' : AUTOMATED},
+ {'step_number' : 3, 'actions' : "Step action 3 -b " ,
+ 'expected_results' : "Step result 3 - b", 'execution_type' : AUTOMATED},
+ {'step_number' : 4, 'actions' : "Step action 4 -b " ,
+ 'expected_results' : "Step result 4 - b", 'execution_type' : AUTOMATED},
+ {'step_number' : 5, 'actions' : "Step action 5 -b " ,
+ 'expected_results' : "Step result 5 - b", 'execution_type' : AUTOMATED}]
+
+newTestCase = myTestLink.createTestCase(NEWTESTCASE_B, newTestSuiteID_A,
+ newProjectID, myTestUserName, "This is the summary of the Test Case B",
+ steps_tc_b, preconditions='these are the preconditions',
+ executiontype=AUTOMATED, status=REWORK, estimatedexecduration=0.5)
+print("createTestCase TC-B in TS-A", newTestCase)
+newTestCaseID_B = newTestCase[0]['id']
+print("New Test Case '%s' - id: %s" % (NEWTESTCASE_B, newTestCaseID_B))
+
+# Move test case TC_B to correct test suite B
+tc_b_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0]['full_tc_external_id']
+response = myTestLink.setTestCaseTestSuite(tc_b_full_ext_id, newTestSuiteID_B)
+print("setTestCaseTestSuite TC-B to TS-B" , response)
+
+# Add test cases to test plan - we need the full external id !
+# for every test case version 1 is used
+tc_version=1
+# TC AA should be tested with platforms 'Big Bird'+'Small Bird'
+response = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)
+print("getTestCase", response)
+tc_aa_full_ext_id = response[0]['full_tc_external_id']
+response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_A,
+ tc_aa_full_ext_id, tc_version, platformid=newPlatFormID_A)
+print("addTestCaseToTestPlan", response)
+tc_aa_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0]['full_tc_external_id']
+response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_A,
+ tc_aa_full_ext_id, tc_version, platformid=newPlatFormID_B)
+print("addTestCaseToTestPlan", response)
+# change test case TC_AA - delete step 8 (step 7 does not exist)
+response = myTestLink.deleteTestCaseSteps(tc_aa_full_ext_id, [7,8],
+ version=tc_version)
+print("deleteTestCaseSteps", response)
+
+# TC B should be tested with platform 'Small Bird'
+tc_b_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0]['full_tc_external_id']
+response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_A,
+ tc_b_full_ext_id, tc_version, platformid=newPlatFormID_B)
+print("addTestCaseToTestPlan", response)
+
+#Update test case TC_B -> high, change step 5, new step 6
+steps_tc_b_v1u = steps_tc_b[:4]
+steps_tc_b_v1u.append(
+ {'step_number' : 5, 'actions' : "Step action 5 -b changed by updateTestCase" ,
+ 'expected_results' : "Step result 5 - b changed", 'execution_type' : AUTOMATED})
+steps_tc_b_v1u.append(
+ {'step_number' : 6, 'actions' : "Step action 6 -b added by updateTestCase" ,
+ 'expected_results' : "Step result 6 - b added", 'execution_type' : AUTOMATED})
+response = myTestLink.updateTestCase(tc_b_full_ext_id, version=tc_version,
+ steps=steps_tc_b_v1u, importance=MEDIUM, estimatedexecduration=3)
+print("updateTestCase", response)
+
+# create additional steps via createTestCaseSteps - action create
+steps_tc_b_c67 = [
+ {'step_number' : 6, 'actions' : "action 6 createTestCaseSteps.create" ,
+ 'expected_results' : "skip - cause step 6 already exist", 'execution_type' : AUTOMATED},
+ {'step_number' : 7, 'actions' : "action 7 createTestCaseSteps.create" ,
+ 'expected_results' : "create - cause step 7 not yet exist", 'execution_type' : AUTOMATED}]
+response = myTestLink.createTestCaseSteps('create', steps_tc_b_c67,
+ testcaseexternalid=tc_b_full_ext_id, version=tc_version)
+print("createTestCaseSteps.create", response)
+# create additional steps via createTestCaseSteps - action update
+steps_tc_b_c38 = [
+ {'step_number' : 3, 'actions' : "action 3 createTestCaseSteps.update" ,
+ 'expected_results' : "update - cause step 3 already exist", 'execution_type' : AUTOMATED},
+ {'step_number' : 8, 'actions' : "action 8 createTestCaseSteps.update" ,
+ 'expected_results' : "create - cause step 8 not yet exist", 'execution_type' : AUTOMATED}]
+response = myTestLink.createTestCaseSteps('update', steps_tc_b_c38,
+ testcaseid=newTestCaseID_B, version=tc_version)
+print("createTestCaseSteps.update", response)
+
+
+# In test plan B TC B should be tested without platform
+response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_B,
+ tc_b_full_ext_id, tc_version)
+print("addTestCaseToTestPlan", response)
+
+# # Try to Remove Platform 'Big Bird' from platform
+# response = myTestLink.removePlatformFromTestPlan(newTestPlanID_A, NEWPLATFORM_C)
+# print "removePlatformFromTestPlan", response
+
+# Remove Platform 'Ugly Bird' from platform
+response = myTestLink.removePlatformFromTestPlan(newTestPlanID_A, NEWPLATFORM_C)
+print("removePlatformFromTestPlan", response)
+
+
+# -- Create Build for TestPlan A (uses platforms)
+newBuild = myTestLink.createBuild(newTestPlanID_A, NEWBUILD_A,
+ buildnotes='Build for TestPlan with platforms')
+print("createBuild", newBuild)
+newBuildID_A = newBuild[0]['id']
+print("New Build '%s' - id: %s" % (NEWBUILD_A, newBuildID_A))
+
+# assign user to test case execution tasks - test plan with platforms
+response = myTestLink.assignTestCaseExecutionTask( myTestUserName,
+ newTestPlanID_A, tc_aa_full_ext_id,
+ buildid=newBuildID_A, platformname=NEWPLATFORM_A)
+print("assignTestCaseExecutionTask", response)
+response = myTestLink.assignTestCaseExecutionTask( myTestUserName2,
+ newTestPlanID_A, tc_aa_full_ext_id,
+ buildname=NEWBUILD_A, platformid=newPlatFormID_B)
+print("assignTestCaseExecutionTask", response)
+response = myTestLink.assignTestCaseExecutionTask( myTestUserName,
+ newTestPlanID_A, tc_b_full_ext_id,
+ buildname=NEWBUILD_A, platformname=NEWPLATFORM_B)
+print("assignTestCaseExecutionTask", response)
+
+# get test case assigned tester
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_A, tc_aa_full_ext_id,
+ buildid=newBuildID_A, platformname=NEWPLATFORM_A)
+print("getTestCaseAssignedTester TC_AA TP_A Platform A", response)
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_A, tc_aa_full_ext_id,
+ buildname=NEWBUILD_A, platformid=newPlatFormID_B)
+print("getTestCaseAssignedTester TC_AA TP_A Platform B", response)
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_A, tc_b_full_ext_id,
+ buildname=NEWBUILD_A, platformname=NEWPLATFORM_B)
+print("getTestCaseAssignedTester TC_B TP_A Platform B", response)
+
+# get bugs for test case TC_AA in test plan A - state TC not executed
+response = myTestLink.getTestCaseBugs(newTestPlanID_A,
+ testcaseexternalid=tc_aa_full_ext_id)
+print("getTestCaseBugs TC_AA in TP_A (TC is not executed)", response)
+
+# report Test Case Results for platform 'Big Bird' with step results
+# TC_AA failed, build should be guessed, TC identified with external id
+newResult = myTestLink.reportTCResult(newTestPlanID_A, 'f', guess=True,
+ testcaseexternalid=tc_aa_full_ext_id,
+ platformname=NEWPLATFORM_A,
+ execduration=2.9, timestamp='2014-09-18 14:33',
+ steps=[{'step_number' : 3, 'result' : 'p', 'notes' : 'result note for passed step 3'},
+ {'step_number' : 4, 'result' : 'f', 'notes' : 'result note for failed step 4'}] )
+print("reportTCResult", newResult)
+newResultID_AA = newResult[0]['id']
+
+# get bugs for test case TC_AA in test plan A - state TC is executed
+response = myTestLink.getTestCaseBugs(newTestPlanID_A,
+ testcaseexternalid=tc_aa_full_ext_id)
+print("getTestCaseBugs TC_AA in TP_A (TC is executed, no bug)", response)
+
+# report Test Case Results for platform 'Small Bird'
+# TC_AA passed, build should be guessed, TC identified with external id
+newResult = myTestLink.reportTCResult(newTestPlanID_A, 'p', guess=True,
+ testcaseexternalid=tc_aa_full_ext_id,
+ platformid=newPlatFormID_B,
+ execduration='3.2', timestamp='2014-09-19 14:33:02')
+print("reportTCResult", newResult)
+newResultID_AA_p = newResult[0]['id']
+# TC_B passed, explicit build and some notes , TC identified with internal id
+newResult = myTestLink.reportTCResult(newTestPlanID_A, 'p',
+ buildid=newBuildID_A, testcaseid=newTestCaseID_B,
+ platformname=NEWPLATFORM_B, notes="first try")
+print("reportTCResult", newResult)
+newResultID_B = newResult[0]['id']
+
+#FIXME: know 1.9.20_fixed issue
+# testlink.testlinkerrors.TLResponseError: 6002: (uploadExecutionAttachment) - Error inserting attachment on DB
+#
+# E_WARNING base64_decode() expects parameter 1 to be string, array given
+# - in /var/www/html/lib/api/xmlrpc/v1/xmlrpc.class.php - Line 5834
+#
+# # add this python file as Attachemnt to last execution of TC_B with
+# # different filename 'MyPyExampleApiGeneric.py'
+# a_file=open(NEWATTACHMENT_PY)
+# newAttachment = myTestLink.uploadExecutionAttachment(a_file, newResultID_B,
+# title='Textfile Example', description='Text Attachment Example for a TestCase Execution',
+# filename='MyPyExampleApiGeneric.py')
+# print("uploadExecutionAttachment", newAttachment)
+# # add png file as Attachemnt to last execution of TC_AA
+# # !Attention - on WINDOWS use binary mode for none text file
+# # see http://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files
+# a_file=open(NEWATTACHMENT_PNG, mode='rb')
+# newAttachment = myTestLink.uploadExecutionAttachment(a_file, newResultID_AA,
+# title='PNG Example', description='PNG Attachment Example for a TestCase Execution')
+# print("uploadExecutionAttachment", newAttachment)
+
+# -- Create Build for TestPlan B (uses no platforms)
+newBuild = myTestLink.createBuild(newTestPlanID_B, NEWBUILD_B,
+ buildnotes='Build for TestPlan without platforms',
+ releasedate='2016-11-30')
+print("createBuild", newBuild)
+newBuildID_B = newBuild[0]['id']
+print("New Build '%s' - id: %s" % (NEWBUILD_B, newBuildID_B))
+
+# assign user to test case execution tasks - test plans without platforms
+response = myTestLink.assignTestCaseExecutionTask( myTestUserName,
+ newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B)
+print("assignTestCaseExecutionTask", response)
+
+# get test case assigned tester
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B)
+print("getTestCaseAssignedTester TC_B TP_B no Platform", response)
+
+# try to remove not assigned tester
+response = myTestLink.unassignTestCaseExecutionTask(
+ newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B,
+ user=myTestUserName2)
+print("unassignTestCaseExecutionTask not assigned user", response)
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B)
+print("getTestCaseAssignedTester TC_B TP_B no Platform", response)
+
+# try to remove all assigned tester
+response = myTestLink.unassignTestCaseExecutionTask(
+ newTestPlanID_B, tc_b_full_ext_id, buildid=newBuildID_B,
+ action='unassignAll')
+print("unassignTestCaseExecutionTask unassignAll", response)
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B)
+print("getTestCaseAssignedTester TC_B TP_B no Platform", response)
+
+# reassign user to test case execution tasks - test plans without platforms
+response = myTestLink.assignTestCaseExecutionTask( myTestUserName,
+ newTestPlanID_B, tc_b_full_ext_id, buildid=newBuildID_B)
+print("assignTestCaseExecutionTask", response)
+response = myTestLink.getTestCaseAssignedTester(
+ newTestPlanID_B, tc_b_full_ext_id, buildname=NEWBUILD_B)
+print("getTestCaseAssignedTester TC_B TP_B no Platform", response)
+
+# TC_B in test plan b (without platform)
+# first try failed (with bug), second blocked - all by user myTestUserName
+newResult = myTestLink.reportTCResult(newTestPlanID_B, 'f',
+ buildid=newBuildID_B, testcaseid=newTestCaseID_B, bugid='007',
+ notes="no birds are singing", user=myTestUserName)
+print("reportTCResult", newResult)
+newResultID_B_f = newResult[0]['id']
+newResult = myTestLink.reportTCResult(newTestPlanID_B, 'b',
+ buildid=newBuildID_B, testcaseid=newTestCaseID_B, bugid='008',
+ notes="hungry birds blocks the execution", user=myTestUserName)
+print("reportTCResult", newResult)
+newResultID_B_b = newResult[0]['id']
+# get bugs for test case TC_B in test plan B - state TC is executed with bug
+response = myTestLink.getTestCaseBugs(newTestPlanID_B,
+ testcaseid=newTestCaseID_B)
+print("getTestCaseBugs TC_B in TP_B (TC is executed with 2 bugs)", response)
+
+
+# now we make a mistake and commit the same result a second time
+# and try to delete this mistake
+newResult = myTestLink.reportTCResult(newTestPlanID_B, 'b',
+ buildid=newBuildID_B, testcaseid=newTestCaseID_B,
+ notes="mistake, commit same result a second time")
+print("reportTCResult", newResult)
+newResultID_B_b2 = int(newResult[0]['id'])
+try:
+ # if TL configuration allows deletion of executions, no error will occur
+ response = myTestLink.deleteExecution(newResultID_B_b2)
+ print("deleteExecution", response)
+except TLResponseError as tl_err:
+ if tl_err.code == 232:
+ # TL configuration does not allow deletion of executions
+ pass
+ else:
+ # sh..: another problem occurs
+ raise
+
+# now we try to change the execution types of the test cases
+# - AA from manual -> auto and B from auto -> manual
+newResult = myTestLink.setTestCaseExecutionType(tc_aa_full_ext_id, tc_version,
+ newProjectID, AUTOMATED)
+print("setTestCaseExecutionType", response)
+newResult = myTestLink.setTestCaseExecutionType(tc_b_full_ext_id, tc_version,
+ newProjectID, MANUAL)
+print("setTestCaseExecutionType", response)
+
+# create TestPlan C with Platform, Build , TestCase, assigned TestCase
+# and delete it
+newTestPlan = myTestLink.createTestPlan(NEWTESTPLAN_C, NEWPROJECT,
+ notes='TestPlan for delete test.',
+ active=1, public=1)
+print("createTestPlan for DeleteTest", newTestPlan)
+newTestPlanID_C = newTestPlan[0]['id']
+print("Test Plan '%s' - id: %s" % (NEWTESTPLAN_C,newTestPlanID_C))
+newBuild = myTestLink.createBuild(newTestPlanID_C, NEWBUILD_C,
+ buildnotes='Build for TestPlan delete test')
+print("createBuild for DeleteTest", newBuild)
+newBuildID_C = newBuild[0]['id']
+print("Build '%s' - id: %s" % (NEWBUILD_C, newBuildID_C))
+response = myTestLink.addPlatformToTestPlan(newTestPlanID_C, NEWPLATFORM_C)
+print("addPlatformToTestPlan", response)
+response = myTestLink.addTestCaseToTestPlan(newProjectID, newTestPlanID_C,
+ tc_aa_full_ext_id, tc_version, platformid=newPlatFormID_C)
+print("addTestCaseToTestPlan", response)
+response = myTestLink.assignTestCaseExecutionTask( myTestUserName,
+ newTestPlanID_C, tc_aa_full_ext_id, buildid=newBuildID_C,
+ platformid=newPlatFormID_C)
+print("assignTestCaseExecutionTask", response)
+newResult = myTestLink.reportTCResult(newTestPlanID_C, 'p',
+ buildid=newBuildID_C, testcaseid=newTestCaseID_AA,
+ platformname=NEWPLATFORM_C, notes="TP delete test")
+print("reportTCResult", newResult)
+newResultID_B = newResult[0]['id']
+
+#FIXME: know 1.9.20_fixed issue
+# testlink.testlinkerrors.TLResponseError: 6002: (uploadExecutionAttachment) - Error inserting attachment on DB
+#
+# E_WARNING base64_decode() expects parameter 1 to be string, array given
+# - in /var/www/html/lib/api/xmlrpc/v1/xmlrpc.class.php - Line 5834
+#
+# newAttachment = myTestLink.uploadExecutionAttachment(NEWATTACHMENT_PY, newResultID_B,
+# title='Textfile Example', filename='MyPyTPDeleteTest.py',
+# description='Attachment Example for a TC Execution and TP delete test')
+# print("uploadExecutionAttachment", newAttachment)
+response = myTestLink.getTotalsForTestPlan(newTestPlanID_C)
+print("getTotalsForTestPlan before delete", response)
+response = myTestLink.deleteTestPlan(newTestPlanID_C)
+print("deleteTestPlan", response)
+try:
+ response = myTestLink.getTotalsForTestPlan(newTestPlanID_C)
+ print("getTotalsForTestPlan after delete", response)
+except TLResponseError as tl_err:
+ print(tl_err.message)
+
+# -- Create Build D and copy Testers from Build A
+newBuild = myTestLink.createBuild(newTestPlanID_A, NEWBUILD_D,
+ buildnotes='Build with copied testers from Build ' + NEWBUILD_A,
+ active=1, open=1, copytestersfrombuild=newBuildID_A)
+print("createBuild", newBuild)
+newBuildID_D = newBuild[0]['id']
+print("New Build '%s' - id: %s" % (NEWBUILD_D, newBuildID_D))
+
+# close build A - buildid must be converted to an integer
+response = myTestLink.closeBuild( int(newBuildID_A) )
+print("closeBuild", response)
+
+# get information - TestProject
+response = myTestLink.getTestProjectByName(NEWPROJECT)
+print("getTestProjectByName", response)
+response = myTestLink.getProjectTestPlans(newProjectID)
+print("getProjectTestPlans", response)
+response = myTestLink.getFirstLevelTestSuitesForTestProject(newProjectID)
+print("getFirstLevelTestSuitesForTestProject", response)
+response = myTestLink.getProjectPlatforms(newProjectID)
+print("getProjectPlatforms", response)
+response = myTestLink.getProjectKeywords(newProjectID)
+print("getProjectKeywords", response)
+
+# get information - TestPlan
+response = myTestLink.getTestPlanByName(NEWPROJECT, NEWTESTPLAN_A)
+print("getTestPlanByName", response)
+response = myTestLink.getTotalsForTestPlan(newTestPlanID_A)
+print("getTotalsForTestPlan", response)
+response = myTestLink.getBuildsForTestPlan(newTestPlanID_A)
+print("getBuildsForTestPlan", response)
+response = myTestLink.getLatestBuildForTestPlan(newTestPlanID_A)
+print("getLatestBuildForTestPlan", response)
+response = myTestLink.getTestPlanPlatforms(newTestPlanID_A)
+print("getTestPlanPlatforms", response)
+response = myTestLink.getTestSuitesForTestPlan(newTestPlanID_A)
+print("getTestSuitesForTestPlan", response)
+# get failed Testcases
+response = myTestLink.getTestCasesForTestPlan(newTestPlanID_A, executestatus='f')
+print("getTestCasesForTestPlan A failed ", response)
+# get Testcases for Plattform SmallBird
+response = myTestLink.getTestCasesForTestPlan(newTestPlanID_A, platformid=newPlatFormID_B)
+print("getTestCasesForTestPlan A SmallBirds", response)
+
+# get information - TestSuite
+response = myTestLink.getTestSuiteByID(newTestSuiteID_B)
+print("getTestSuiteByID", response)
+response = myTestLink.getTestSuitesForTestSuite(newTestSuiteID_A)
+print("getTestSuitesForTestSuite", response)
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A,
+ deep=True, details='full')
+print("getTestCasesForTestSuite", response)
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B,
+ deep=False, details='only_id')
+print("getTestCasesForTestSuite", response)
+
+# Update test suite B details - Using Project ID
+updatedTestSuite = myTestLink.updateTestSuite(newTestSuiteID_B,
+ testprojectid=newProjectID,
+ details="updated Details of the Test Suite B")
+print("updateTestSuite", updatedTestSuite)
+
+# Update test suite A name and order details - Using Project Name
+# with TL 1.9.15 this step fails - solution see TL Mantis Ticket 7696
+#
+changedNEWTESTSUITE_A = NEWTESTSUITE_A + ' - Changed'
+updatedTestSuite = myTestLink.updateTestSuite(newTestSuiteID_A, prefix=NEWPREFIX,
+ testsuitename = changedNEWTESTSUITE_A, order=1)
+print("updateTestSuite", updatedTestSuite)
+
+# get all test suites, using the same name - test Suite B
+response = myTestLink.getTestSuite(NEWTESTSUITE_B, NEWPREFIX)
+print("getTestSuite", response)
+
+# get informationen - TestCase_B
+response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, testprojectname=NEWPROJECT)
+print("getTestCaseIDByName", response)
+# get informationen - TestCase_AA via Pathname
+tcpathname = '::'.join([NEWPROJECT, changedNEWTESTSUITE_A, NEWTESTSUITE_AA, NEWTESTCASE_AA])
+response = myTestLink.getTestCaseIDByName('unknown', testcasepathname=tcpathname)
+print("getTestCaseIDByName", response)
+# get execution result
+response = myTestLink.getLastExecutionResult(newTestPlanID_A,
+ testcaseexternalid=tc_aa_full_ext_id)
+print("getLastExecutionResult", response)
+response = myTestLink.getExecutionSet(newTestPlanID_A,
+ testcaseexternalid=tc_aa_full_ext_id)
+print("getExecutionSet", response)
+response = myTestLink.getLastExecutionResult(newTestPlanID_A,
+ testcaseid=newTestCaseID_B)
+print("getLastExecutionResult", response)
+response = myTestLink.getExecutionSet(newTestPlanID_A,
+ testcaseid=newTestCaseID_B)
+print("getExecutionSet", response)
+
+response = myTestLink.getLastExecutionResult(newTestPlanID_A,
+ testcaseid=newTestCaseID_AA, platformid=newPlatFormID_A)
+print("getLastExecutionResult", response)
+response = myTestLink.getExecutionSet(newTestPlanID_A,
+ testcaseid=newTestCaseID_AA, platformid=newPlatFormID_A)
+print("getExecutionSet", response)
+
+response = myTestLink.getAllExecutionsResults(newTestPlanID_A,
+ testcaseid=newTestCaseID_AA, options={'getBugs' : 1})
+print("getAllExecutionsResults", response)
+response = myTestLink.getAllExecutionsResults(newTestPlanID_B,
+ testcaseid=newTestCaseID_B, options={'getBugs' : 1})
+print("getAllExecutionsResults", response)
+
+response = myTestLink.getExecCountersByBuild(newTestPlanID_A)
+print("getExecCountersByBuild", response)
+response = myTestLink.getExecCountersByBuild(newTestPlanID_B)
+print("getExecCountersByBuild", response)
+response = myTestLink.getTestCaseKeywords(testcaseexternalid=tc_b_full_ext_id)
+print("getTestCaseKeywords noKeyWords", response)
+
+# get information - general
+response = myTestLink.getFullPath(int(newTestSuiteID_AA))
+print("getFullPath", response)
+response = myTestLink.getFullPath([int(newTestCaseID_AA), int(newTestCaseID_B)])
+print("getFullPath", response)
+
+# attachments
+# add png file as Attachment to test project
+a_file=open(NEWATTACHMENT_PNG, mode='rb')
+newAttachment = myTestLink.uploadTestProjectAttachment(a_file, newProjectID,
+ title='PNG Example', description='PNG Attachment Example for a TestProject')
+print("uploadTestProjectAttachment", newAttachment)
+# add png file as Attachnent to test suite A - uploadXyzAttachmemt also file path
+newAttachment = myTestLink.uploadTestSuiteAttachment(NEWATTACHMENT_PNG, newTestSuiteID_A,
+ title='PNG Example', description='PNG Attachment Example for a TestSuite')
+print("uploadTestSuiteAttachment", newAttachment)
+# get Attachment of test suite A
+response = myTestLink.getTestSuiteAttachments(newTestSuiteID_A)
+print("getTestSuiteAttachments", response)
+
+# FIXME: know 1.9.19 issue
+# E_WARNING base64_decode() expects parameter 1 to be string, array given
+# - in /var/www/html/lib/api/xmlrpc/v1/xmlrpc.class.php - Line 5103
+#
+# # add png file as Attachment to test case B
+# a_file=open(NEWATTACHMENT_PNG, mode='rb')
+# newAttachment = myTestLink.uploadTestCaseAttachment(a_file, newTestCaseID_B, 1,
+# title='PNG Example', description='PNG Attachment Example for a TestCase')
+# print("uploadTestCaseAttachment", newAttachment)
+# # get Attachment of test case B
+# # response = myTestLink.getTestCaseAttachments(testcaseexternalid=tc_aa_full_ext_id)
+# # print "getTestCaseAttachments", response
+# response = myTestLink.getTestCaseAttachments(testcaseid=newTestCaseID_B, version=1)
+# print("getTestCaseAttachments v1", response)
+# response = myTestLink.getTestCaseAttachments(testcaseid=newTestCaseID_B)
+# print("getTestCaseAttachments vNone", response)
+
+# get requirements for the test project - empty result
+response = myTestLink.getRequirements(newProjectID)
+print("getRequirements test project", "Sorry currently no requirments", response)
+
+# get requirements for the test plan - empty result
+response = myTestLink.getRequirements(newProjectID,
+ testplanid = newTestPlanID_A,
+ platformid = newPlatFormID_B)
+print("getRequirements test plan", "Sorry currently no requirments", response)
+
+
+print("")
+print("Number of Projects in TestLink: %i " % len(myTestLink.getProjects()))
+print("")
+for project in myTestLink.getProjects():
+ print("Name: %(name)s ID: %(id)s " % project)
+print("")
+
+
+#
+#
+#
diff --git a/example/TestLinkExampleGenericApi_Req.py b/example/TestLinkExampleGenericApi_Req.py
new file mode 100644
index 0000000..10991b7
--- /dev/null
+++ b/example/TestLinkExampleGenericApi_Req.py
@@ -0,0 +1,348 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2017-2019 Luiko Czub, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+
+"""
+
+Shows how to use the TestLinkAPIGeneric for requirements
+This example requires a special existing project with special custom fields
+assigned and a set of requirements
+
+a) run example TestLinkExampleGenericApi.py
+ - this creates a project like PROJECT_API_GENERIC-36
+b) load custom field definitions customFields_ExampleDefs.xml
+ TL - Desktop - System - Define Custom Fields - Import
+c) assign custom fields to project PROJECT_API_GENERIC-36
+ TL - Desktop - Test Project - Assign Custom Fields
+d) load requirement definitions all-req.xml to project PROJECT_API_GENERIC-36
+ TL - Desktop - Requirements - Requirement Specification
+
+Script works with:
+
+TestProject PROJECT_API_GENERIC-36
+- TestSuite B - First Level
+ - TestCase TESTCASE_B
+- TestPlan TestPlan_API_GENERIC A (Platform Small Bird)
+ - Build TestlinkAPIGeneric v0.x.y
+
+Script creates custom values for TestCase TESTCASE_B
+- scope test specification and test execution
+
+Script returns custom field values from TestPlan and TestSuite, if the user has
+added manually some values.
+
+Cause of missing knowledge, how ids of kind
+- requirement and requirement specifications
+- testplan - testcase link
+could be requested via api, these example does not work currently.
+
+Script adds keywords KeyWord01 KeyWord02 KeyWord03 to test case TESTCASE_B,
+removes keyword KeyWord02 again.
+
+Script adds keywords KeyWord01 KeyWord02 to test case TESTCASE_AA,
+removes keyword KeyWord01 again.
+
+"""
+from testlink import TestlinkAPIGeneric, TestLinkHelper
+from testlink.testlinkerrors import TLResponseError
+import sys, os.path
+from platform import python_version
+
+# precondition a)
+# SERVER_URL and KEY are defined in environment
+# TESTLINK_API_PYTHON_SERVER_URL=http://YOURSERVER/testlink/lib/api/xmlrpc.php
+# TESTLINK_API_PYTHON_DEVKEY=7ec252ab966ce88fd92c25d08635672b
+#
+# alternative precondition b)
+# SERVEUR_URL and KEY are defined as command line arguments
+# python TestLinkExample.py --server_url http://YOURSERVER/testlink/lib/api/xmlrpc.php
+# --devKey 7ec252ab966ce88fd92c25d08635672b
+#
+# ATTENTION: With TestLink 1.9.7, cause of the new REST API, the SERVER_URL
+# has changed from
+# (old) http://YOURSERVER/testlink/lib/api/xmlrpc.php
+# to
+# (new) http://YOURSERVER/testlink/lib/api/xmlrpc/v1/xmlrpc.php
+tl_helper = TestLinkHelper()
+tl_helper.setParamsFromArgs('''Shows how to use the TestLinkAPI for CustomFields.
+=> requires an existing project PROJECT_API_GENERIC-*''')
+myTestLink = tl_helper.connect(TestlinkAPIGeneric)
+
+myPyVersion = python_version()
+myPyVersionShort = myPyVersion.replace('.', '')[:2]
+
+NEWTESTPLAN_A="TestPlan_API_GENERIC A"
+# NEWTESTPLAN_B="TestPlan_API_GENERIC B"
+# NEWTESTPLAN_C="TestPlan_API_GENERIC C - DeleteTest"
+# NEWPLATFORM_A='Big Bird %s' % myPyVersionShort
+NEWPLATFORM_B='Small Bird'
+# NEWPLATFORM_C='Ugly Bird'
+NEWTESTSUITE_A="A - First Level"
+NEWTESTSUITE_B="B - First Level"
+NEWTESTSUITE_AA="AA - Second Level"
+NEWTESTCASE_AA="TESTCASE_AA"
+NEWTESTCASE_B="TESTCASE_B"
+# myApiVersion='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__)
+# NEWBUILD_A='%s' % myApiVersion
+# NEWBUILD_B='%s' % myApiVersion
+# NEWBUILD_C='%s - DeleteTest' % myApiVersion
+# NEWBUILD_D='%s - copyTestersTest' % myApiVersion
+
+this_file_dirname=os.path.dirname(__file__)
+NEWATTACHMENT_PY= os.path.join(this_file_dirname, 'TestLinkExampleGenericApi.py')
+NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png')
+
+# Servers TestLink Version
+myTLVersion = myTestLink.testLinkVersion()
+myTLVersionShort = myTLVersion.replace('.', '')
+
+NEWPROJECT="PROJECT_API_GENERIC-%s" % myPyVersionShort
+NEWPREFIX="GPROAPI%s" % myPyVersionShort
+ITSNAME="myITS"
+
+# used connection settings
+print( myTestLink.connectionInfo() )
+print( "" )
+
+# get information - TestProject
+newProject = myTestLink.getTestProjectByName(NEWPROJECT)
+print( "getTestProjectByName", newProject )
+newProjectID = newProject['id']
+print( "Project '%s' - id: %s" % (NEWPROJECT,newProjectID) )
+response = myTestLink.getProjectKeywords(newProjectID)
+print("getProjectKeywords", response)
+
+# get information - TestPlan
+newTestPlan = myTestLink.getTestPlanByName(NEWPROJECT, NEWTESTPLAN_A)
+print( "getTestPlanByName", newTestPlan )
+newTestPlanID_A = newTestPlan[0]['id']
+print( "Test Plan '%s' - id: %s" % (NEWTESTPLAN_A,newTestPlanID_A) )
+response = myTestLink.getTotalsForTestPlan(newTestPlanID_A)
+print( "getTotalsForTestPlan", response )
+response = myTestLink.getBuildsForTestPlan(newTestPlanID_A)
+print( "getBuildsForTestPlan", response )
+newBuildID_A = response[0]['id']
+newBuildName_A = response[0]['name']
+# get information - TestSuite
+response = myTestLink.getTestSuitesForTestPlan(newTestPlanID_A)
+print( "getTestSuitesForTestPlan", response )
+newTestSuiteID_A=response[0]['id']
+newTestSuiteID_AA=response[1]['id']
+newTestSuiteID_B=response[2]['id']
+newTestSuite = myTestLink.getTestSuiteByID(newTestSuiteID_B)
+print( "getTestSuiteByID", newTestSuite )
+# get informationen - TestCase_B
+response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, testprojectname=NEWPROJECT)
+print( "getTestCaseIDByName", response )
+newTestCaseID_B = response[0]['id']
+tc_b_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0]['full_tc_external_id']
+print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_B, newTestCaseID_B, tc_b_full_ext_id) )
+# get informationen - TestCase_AA
+response = myTestLink.getTestCaseIDByName(NEWTESTCASE_AA, testprojectname=NEWPROJECT)
+print( "getTestCaseIDByName", response )
+newTestCaseID_AA = response[0]['id']
+tc_aa_full_ext_id = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0]['full_tc_external_id']
+print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_AA, newTestCaseID_AA, tc_aa_full_ext_id) )
+
+
+# add keywords to TestCase B and TestCase AA
+response = myTestLink.addTestCaseKeywords(
+ {tc_b_full_ext_id : ['KeyWord01', 'KeyWord03', 'KeyWord02'],
+ tc_aa_full_ext_id : ['KeyWord01', 'KeyWord02', 'KeyWord03']})
+print( "addTestCaseKeywords", response )
+# remove keywords from TestCase B and TestCase AA
+response = myTestLink.removeTestCaseKeywords(
+ {tc_b_full_ext_id : ['KeyWord02'],
+ tc_aa_full_ext_id : ['KeyWord01', 'KeyWord03']})
+print( "removeTestCaseKeywords", response )
+
+
+# list test cases with assigned keywords B
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, deep=True,
+ details='full', getkeywords=True)
+print( "getTestCasesForTestSuite B (deep=True)", response )
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, deep=False,
+ details='full', getkeywords=True)
+print( "getTestCasesForTestSuite B (deep=False)", response )
+
+# get informationen - TestCase_B again
+newTestCase_B = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0]
+print( "getTestCase B", newTestCase_B )
+
+# # return keyword list for TestCase_B - service method TestLinkAPIClient
+# response = myTestLink.listKeywordsForTC(newTestCaseID_B)
+# print( "listKeywordsForTC B", response )
+# # return keyword lists for all test cases of test newTestSuite_B - service method TestLinkAPIClient
+# response = myTestLink.listKeywordsForTS(newTestSuiteID_B)
+# print( "listKeywordsForTS B", response )
+
+# list test cases with assigned keywords AA
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, deep=True,
+ details='full', getkeywords=True)
+print( "getTestCasesForTestSuite A (deep=True)", response )
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, deep=False,
+ details='full', getkeywords=True)
+print( "getTestCasesForTestSuite A (deep=False)", response )
+
+# get informationen - TestCase_AA again
+newTestCase_AA = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0]
+print( "getTestCase AA", newTestCase_AA )
+
+# # return keyword list for TestCase_AA - service method TestLinkAPIClient
+# response = myTestLink.listKeywordsForTC(newTestCaseID_AA)
+# print( "listKeywordsForTC AA", response ) - service method TestLinkAPIClient
+# # return keyword lists for all test cases of test newTestSuite_A
+# response = myTestLink.listKeywordsForTS(newTestSuiteID_AA)
+# print( "listKeywordsForTS AA", response )
+
+
+response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_B)
+print("getTestCaseKeywords B", response)
+response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_AA)
+print("getTestCaseKeywords AA", response)
+
+# new execution result with custom field data
+# TC_B passed, explicit build and some notes , TC identified with internal id
+newResult = myTestLink.reportTCResult(newTestPlanID_A, 'p',
+ buildname=newBuildName_A, testcaseid=newTestCaseID_B,
+ platformname=NEWPLATFORM_B, notes="bugid 4711 is assigned",
+ bugid='4711',
+ customfields={'cf_tc_ex_string' : 'a custom exec value set via api',
+ 'cf_tc_sd_listen' : 'ernie'})
+print( "reportTCResult", newResult )
+
+# get execution results
+lastResult = myTestLink.getLastExecutionResult(newTestPlanID_A,
+ testcaseid=newTestCaseID_B,
+ options={'getBugs' : True})[0]
+print( "getLastExecutionResult", lastResult )
+
+# get all requirement for the testprojekt
+req_list = myTestLink.getRequirements(newProjectID)
+print ( "getRequirements all", req_list )
+reqA = req_list[0]
+reqB = req_list[1]
+
+# add requirement reqA to testcase B
+# response = myTestLink.assignRequirements(newTestCase_B['full_tc_external_id'], newProjectID,
+# [{'req_spec' : reqA['srs_id'], 'requirements' : [ reqA['id'] ]} ]
+# )
+print("assignRequirements reqA to TC-B",
+ "sorry not possible - required srs_id -rec_spec(id) not available")
+
+# get coverage for requirements reqA
+response = myTestLink.getReqCoverage(newProjectID, reqA['req_doc_id'])
+print("getReqCoverage reqA", response)
+
+# add png file as Attachemnt to a requirement specification.
+print("uploadRequirementSpecificationAttachment",
+ "sorry not possible - required srs_id -rec_spec(id) not available")
+# add png file as Attachemnt to a requirement.
+#a_file=open(NEWATTACHMENT_PNG, mode='rb')
+newAttachment = myTestLink.uploadRequirementAttachment(NEWATTACHMENT_PNG, reqA['id'],
+ title='PNG Example', description='PNG Attachment Example for a requirement')
+print("uploadRequirementAttachment", newAttachment)
+
+
+# map of used ids
+args = {'devKey' : myTestLink.devKey,
+ 'testprojectid' : newProjectID,
+ 'testcaseexternalid' : newTestCase_B['full_tc_external_id'],
+ 'version' : int(newTestCase_B['version']),
+ 'tcversion_number' : lastResult['tcversion_number'],
+ 'executionid' : lastResult['id'],
+ 'linkid' : 779,
+ 'testsuiteid': newTestSuiteID_B,
+ 'testplanid': lastResult['testplan_id'],
+ 'reqspecid': 7789,
+# 'reqspecid': reqA['srs_id'],
+ 'requirementid': reqA['id'],
+ 'buildid':newBuildID_A}
+
+# get CustomField Value - TestCase Execution
+response = myTestLink.getTestCaseCustomFieldExecutionValue(
+ 'cf_tc_ex_string', args['testprojectid'], args['tcversion_number'],
+ args['executionid'] , args['testplanid'] )
+print( "getTestCaseCustomFieldExecutionValue", response )
+
+# update CustomField Value - TestCase SpecDesign
+response = myTestLink.updateTestCaseCustomFieldDesignValue(
+ args['testcaseexternalid'], args['version'],
+ args['testprojectid'],
+ {'cf_tc_sd_string' : 'A custom SpecDesign value set via api',
+ 'cf_tc_sd_list' : 'bibo'})
+print( "updateTestCaseCustomFieldDesignValue", response )
+
+# get CustomField Value - TestCase SpecDesign
+#response = myTestLink._callServer('getTestCaseCustomFieldDesignValue', args)
+response = myTestLink.getTestCaseCustomFieldDesignValue(
+ args['testcaseexternalid'], args['version'],
+ args['testprojectid'], 'cf_tc_sd_string', details='full')
+print( "getTestCaseCustomFieldDesignValue full", response )
+
+response = myTestLink.getTestCaseCustomFieldDesignValue(
+ args['testcaseexternalid'], args['version'],
+ args['testprojectid'], 'cf_tc_sd_string', details='value')
+print( "getTestCaseCustomFieldDesignValue value", response )
+
+response = myTestLink.getTestCaseCustomFieldDesignValue(
+ args['testcaseexternalid'], args['version'],
+ args['testprojectid'], 'cf_tc_sd_list', details='simple')
+print( "getTestCaseCustomFieldDesignValue simple", response )
+
+# get CustomField Value - TestCase Testplan Design
+response = myTestLink.getTestCaseCustomFieldTestPlanDesignValue(
+ 'cf_tc_pd_string', args['testprojectid'], args['tcversion_number'],
+ args['testplanid'], args['linkid'])
+print( "getTestCaseCustomFieldTestPlanDesignValue", response )
+
+# update CustomField Value - TestSuite SpecDesign
+response = myTestLink.updateTestSuiteCustomFieldDesignValue(
+ args['testprojectid'], args['testsuiteid'],
+ {'cf_ts_string' : 'A custom TestSuite value set via api'})
+print( "updateTestSuiteCustomFieldDesignValue", response )
+
+# get CustomField Value - TestSuite
+response = myTestLink.getTestSuiteCustomFieldDesignValue(
+ 'cf_ts_string', args['testprojectid'], args['testsuiteid'])
+print( "getTestSuiteCustomFieldDesignValue", response )
+
+# get CustomField Value - TestPlan
+response = myTestLink.getTestPlanCustomFieldDesignValue(
+ 'cf_tp_string', args['testprojectid'], args['testplanid'])
+print( "getTestPlanCustomFieldDesignValue", response )
+
+# get CustomField Value - Requirement Specification
+response = myTestLink.getReqSpecCustomFieldDesignValue(
+ 'cf_req_sd_string', args['testprojectid'], args['reqspecid'])
+print( "getReqSpecCustomFieldDesignValue", response )
+
+
+# get CustomField Value - Requirement Specification
+response = myTestLink.getRequirementCustomFieldDesignValue(
+ 'cf_req_string',args['testprojectid'], args['requirementid'])
+print( "getRequirementCustomFieldDesignValue", response )
+
+# update CustomField Value - Build
+response = myTestLink.updateBuildCustomFieldsValues(
+ args['testprojectid'], args['testplanid'], args['buildid'],
+ {'cf_b_string' : 'A custom Build value set via api'})
+print( "updateBuildCustomFieldsValues", response )
+
+
diff --git a/example/TestLinkExample_CF_KW.py b/example/TestLinkExample_CF_KW.py
new file mode 100644
index 0000000..b7e19aa
--- /dev/null
+++ b/example/TestLinkExample_CF_KW.py
@@ -0,0 +1,326 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+
+"""
+
+Shows how to use the TestLinkAPI for custom fields
+This example requires a special existing project with special custom fields
+assigned
+
+a) run example TestLinkExample.py
+ - this creates a project like NEW_PROJECT_API-37
+b) load custom field definitions customFields_ExampleDefs.xml
+ TL - Desktop - System - Define Custom Fields - Import
+c) assign custom fields to project NEW_PROJECT_API-37
+ TL - Desktop - Test Project - Assign Custom Fields
+d) load keyword definitions keywords_ExampleDefs.xml
+ TL - Desktop - Test Project - Keyword Management
+
+Script works with:
+
+TestProject NEW_PROJECT_API-37
+- TestSuite B - First Level
+ - TestCase TESTCASE_B
+- TestPlan TestPlan_API A (Platform Small Bird)
+ - Build TestlinkAPIClient v0.x.y
+
+Script creates custom values for TestCase TESTCASE_B
+- scope test specification and test execution
+
+Script returns custom field values from TestPlan and TestSuite, if the user has
+added manually some values.
+
+Cause of missing knowledge, how ids of kind
+- requirement and requirement specifications
+- testplan - testcase link
+could be requested via api, these example does not work currently.
+
+Script adds keywords KeyWord01 KeyWord02 KeyWord03 to test case TESTCASE_B,
+removes keyword KeyWord02 again.
+
+Script adds keywords KeyWord01 KeyWord02 to test case TESTCASE_AA,
+removes keyword KeyWord01 again.
+
+"""
+from testlink import TestlinkAPIClient, TestLinkHelper
+from testlink.testlinkerrors import TLResponseError
+import sys, os.path
+from platform import python_version
+
+# precondition a)
+# SERVER_URL and KEY are defined in environment
+# TESTLINK_API_PYTHON_SERVER_URL=http://YOURSERVER/testlink/lib/api/xmlrpc.php
+# TESTLINK_API_PYTHON_DEVKEY=7ec252ab966ce88fd92c25d08635672b
+#
+# alternative precondition b)
+# SERVEUR_URL and KEY are defined as command line arguments
+# python TestLinkExample.py --server_url http://YOURSERVER/testlink/lib/api/xmlrpc.php
+# --devKey 7ec252ab966ce88fd92c25d08635672b
+#
+# ATTENTION: With TestLink 1.9.7, cause of the new REST API, the SERVER_URL
+# has changed from
+# (old) http://YOURSERVER/testlink/lib/api/xmlrpc.php
+# to
+# (new) http://YOURSERVER/testlink/lib/api/xmlrpc/v1/xmlrpc.php
+tl_helper = TestLinkHelper()
+tl_helper.setParamsFromArgs('''Shows how to use the TestLinkAPI for CustomFields.
+=> requires an existing project NEW_PROJECT_API-*''')
+myTestLink = tl_helper.connect(TestlinkAPIClient)
+
+myPyVersion = python_version()
+myPyVersionShort = myPyVersion.replace('.', '')[:2]
+
+NEWTESTPLAN_A="TestPlan_API A"
+# NEWTESTPLAN_B="TestPlan_API B"
+# NEWTESTPLAN_C="TestPlan_API C - DeleteTest"
+# NEWPLATFORM_A='Big Birds %s' % myPyVersionShort
+NEWPLATFORM_B='Small Birds'
+# NEWPLATFORM_C='Ugly Birds'
+NEWTESTSUITE_A="A - First Level"
+NEWTESTSUITE_B="B - First Level"
+NEWTESTSUITE_AA="AA - Second Level"
+NEWTESTCASE_AA="TESTCASE_AA"
+NEWTESTCASE_B="TESTCASE_B"
+# myApiVersion='%s v%s' % (myTestLink.__class__.__name__ , myTestLink.__version__)
+# NEWBUILD_A='%s' % myApiVersion
+# NEWBUILD_B='%s' % myApiVersion
+# NEWBUILD_C='%s - DeleteTest' % myApiVersion
+# NEWBUILD_D='%s - copyTestersTest' % myApiVersion
+
+this_file_dirname=os.path.dirname(__file__)
+NEWATTACHMENT_PY= os.path.join(this_file_dirname, 'TestLinkExample.py')
+NEWATTACHMENT_PNG=os.path.join(this_file_dirname, 'PyGreat.png')
+
+# Servers TestLink Version
+myTLVersion = myTestLink.testLinkVersion()
+myTLVersionShort = myTLVersion.replace('.', '')
+
+NEWPROJECT="NEW_PROJECT_API-%s" % myPyVersionShort
+NEWPREFIX="NPROAPI%s" % myPyVersionShort
+ITSNAME="myITS"
+
+# used connection settings
+print( myTestLink.connectionInfo() )
+print( "" )
+
+# get information - TestProject
+newProject = myTestLink.getTestProjectByName(NEWPROJECT)
+print( "getTestProjectByName", newProject )
+newProjectID = newProject['id']
+print( "Project '%s' - id: %s" % (NEWPROJECT,newProjectID) )
+response = myTestLink.getProjectKeywords(newProjectID)
+print("getProjectKeywords", response)
+
+# get information - TestPlan
+newTestPlan = myTestLink.getTestPlanByName(NEWPROJECT, NEWTESTPLAN_A)
+print( "getTestPlanByName", newTestPlan )
+newTestPlanID_A = newTestPlan[0]['id']
+print( "Test Plan '%s' - id: %s" % (NEWTESTPLAN_A,newTestPlanID_A) )
+response = myTestLink.getTotalsForTestPlan(newTestPlanID_A)
+print( "getTotalsForTestPlan", response )
+response = myTestLink.getBuildsForTestPlan(newTestPlanID_A)
+print( "getBuildsForTestPlan", response )
+newBuildID_A = response[0]['id']
+newBuildName_A = response[0]['name']
+# get information - TestSuite
+response = myTestLink.getTestSuitesForTestPlan(newTestPlanID_A)
+print( "getTestSuitesForTestPlan", response )
+newTestSuiteID_A=response[0]['id']
+newTestSuiteID_AA=response[1]['id']
+newTestSuiteID_B=response[2]['id']
+newTestSuite = myTestLink.getTestSuiteByID(newTestSuiteID_B)
+print( "getTestSuiteByID", newTestSuite )
+# get informationen - TestCase_B
+response = myTestLink.getTestCaseIDByName(NEWTESTCASE_B, testprojectname=NEWPROJECT)
+print( "getTestCaseIDByName", response )
+newTestCaseID_B = response[0]['id']
+tc_b_full_ext_id = myTestLink.getTestCase(newTestCaseID_B)[0]['full_tc_external_id']
+print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_B, newTestCaseID_B, tc_b_full_ext_id) )
+# get informationen - TestCase_AA
+response = myTestLink.getTestCaseIDByName(NEWTESTCASE_AA, testprojectname=NEWPROJECT)
+print( "getTestCaseIDByName", response )
+newTestCaseID_AA = response[0]['id']
+tc_aa_full_ext_id = myTestLink.getTestCase(newTestCaseID_AA)[0]['full_tc_external_id']
+print( "Test Case '%s' - id: %s - ext-id %s" % (NEWTESTCASE_AA, newTestCaseID_AA, tc_aa_full_ext_id) )
+
+
+# add keywords to TestCase B and TestCase AA
+response = myTestLink.addTestCaseKeywords(
+ {tc_b_full_ext_id : ['KeyWord01', 'KeyWord03', 'KeyWord02'],
+ tc_aa_full_ext_id : ['KeyWord01', 'KeyWord02', 'KeyWord03']})
+print( "addTestCaseKeywords", response )
+# remove keywords from TestCase B and TestCase AA
+response = myTestLink.removeTestCaseKeywords(
+ {tc_b_full_ext_id : ['KeyWord02'],
+ tc_aa_full_ext_id : ['KeyWord01', 'KeyWord03']})
+print( "removeTestCaseKeywords", response )
+
+
+# list test cases with assigned keywords B
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, True,
+ 'full', getkeywords=True)
+print( "getTestCasesForTestSuite B (deep=True)", response )
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_B, False,
+ 'full', getkeywords=True)
+print( "getTestCasesForTestSuite B (deep=False)", response )
+
+# get informationen - TestCase_B again
+newTestCase_B = myTestLink.getTestCase(testcaseid=newTestCaseID_B)[0]
+print( "getTestCase B", newTestCase_B )
+
+# return keyword list for TestCase_B
+response = myTestLink.listKeywordsForTC(newTestCaseID_B)
+print( "listKeywordsForTC B", response )
+# return keyword lists for all test cases of test newTestSuite_B
+response = myTestLink.listKeywordsForTS(newTestSuiteID_B)
+print( "listKeywordsForTS B", response )
+
+# list test cases with assigned keywords AA
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, True,
+ 'full', getkeywords=True)
+print( "getTestCasesForTestSuite A (deep=True)", response )
+response = myTestLink.getTestCasesForTestSuite(newTestSuiteID_A, False,
+ 'full', getkeywords=True)
+print( "getTestCasesForTestSuite A (deep=False)", response )
+
+# get informationen - TestCase_AA again
+newTestCase_AA = myTestLink.getTestCase(testcaseid=newTestCaseID_AA)[0]
+print( "getTestCase AA", newTestCase_AA )
+
+# return keyword list for TestCase_AA
+response = myTestLink.listKeywordsForTC(newTestCaseID_AA)
+print( "listKeywordsForTC AA", response )
+# return keyword lists for all test cases of test newTestSuite_A
+response = myTestLink.listKeywordsForTS(newTestSuiteID_AA)
+print( "listKeywordsForTS AA", response )
+
+
+response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_B)
+print("getTestCaseKeywords B", response)
+response = myTestLink.getTestCaseKeywords(testcaseid=newTestCaseID_AA)
+print("getTestCaseKeywords AA", response)
+
+# new execution result with custom field data
+# TC_B passed, explicit build and some notes , TC identified with internal id
+newResult = myTestLink.reportTCResult(newTestCaseID_B, newTestPlanID_A,
+ newBuildName_A, 'p', "bugid 4711 is assigned",
+ platformname=NEWPLATFORM_B, bugid='4711',
+ customfields={'cf_tc_ex_string' : 'a custom exec value set via api',
+ 'cf_tc_sd_listen' : 'ernie'})
+print( "reportTCResult", newResult )
+
+# get execution results
+lastResult = myTestLink.getLastExecutionResult(newTestPlanID_A, newTestCaseID_B,
+ options={'getBugs' : True})[0]
+print( "getLastExecutionResult", lastResult )
+
+# map of used ids
+args = {'devKey' : myTestLink.devKey,
+ 'testprojectid' : newProjectID,
+ 'testcaseexternalid' : newTestCase_B['full_tc_external_id'],
+ 'version' : int(newTestCase_B['version']),
+ 'tcversion_number' : lastResult['tcversion_number'],
+ 'executionid' : lastResult['id'],
+ 'linkid' : 779,
+ 'testsuiteid': newTestSuiteID_B,
+ 'testplanid': lastResult['testplan_id'],
+ 'reqspecid': 7789,
+ 'requirementid': 7791,
+ 'buildid':newBuildID_A}
+
+# get CustomField Value - TestCase Execution
+response = myTestLink.getTestCaseCustomFieldExecutionValue(
+ 'cf_tc_ex_string', args['testprojectid'], args['tcversion_number'],
+ args['executionid'] , args['testplanid'] )
+print( "getTestCaseCustomFieldExecutionValue", response )
+
+# update CustomField Value - TestCase SpecDesign
+response = myTestLink.updateTestCaseCustomFieldDesignValue(
+ args['testcaseexternalid'], args['version'],
+ args['testprojectid'],
+ {'cf_tc_sd_string' : 'A custom SpecDesign value set via api',
+ 'cf_tc_sd_list' : 'bibo'})
+print( "updateTestCaseCustomFieldDesignValue", response )
+
+# get CustomField Value - TestCase SpecDesign
+#response = myTestLink._callServer('getTestCaseCustomFieldDesignValue', args)
+response = myTestLink.getTestCaseCustomFieldDesignValue(
+ args['testcaseexternalid'], args['version'],
+ args['testprojectid'], 'cf_tc_sd_string', 'full')
+print( "getTestCaseCustomFieldDesignValue full", response )
+
+response = myTestLink.getTestCaseCustomFieldDesignValue(
+ args['testcaseexternalid'], args['version'],
+ args['testprojectid'], 'cf_tc_sd_string', 'value')
+print( "getTestCaseCustomFieldDesignValue value", response )
+
+response = myTestLink.getTestCaseCustomFieldDesignValue(
+ args['testcaseexternalid'], args['version'],
+ args['testprojectid'], 'cf_tc_sd_list', 'simple')
+print( "getTestCaseCustomFieldDesignValue simple", response )
+
+# get CustomField Value - TestCase Testplan Design
+response = myTestLink.getTestCaseCustomFieldTestPlanDesignValue(
+ 'cf_tc_pd_string', args['testprojectid'], args['tcversion_number'],
+ args['testplanid'], args['linkid'])
+print( "getTestCaseCustomFieldTestPlanDesignValue", response )
+
+# get CustomFields Values from all test cases of a TestPlan
+response = myTestLink.getTestCasesForTestPlan(args['testplanid'],
+ customfields=True)
+print("getTestCasesForTestPlan with all customfields", response)
+response = myTestLink.getTestCasesForTestPlan(args['testplanid'],
+ customfields=['cf_tc_sd_string'])
+print("getTestCasesForTestPlan with specific customfields", response)
+
+# update CustomField Value - TestSuite SpecDesign
+response = myTestLink.updateTestSuiteCustomFieldDesignValue(
+ args['testprojectid'], args['testsuiteid'],
+ {'cf_ts_string' : 'A custom TestSuite value set via api'})
+print( "updateTestSuiteCustomFieldDesignValue", response )
+
+# get CustomField Value - TestSuite
+response = myTestLink.getTestSuiteCustomFieldDesignValue(
+ 'cf_ts_string', args['testprojectid'], args['testsuiteid'])
+print( "getTestSuiteCustomFieldDesignValue", response )
+
+# get CustomField Value - TestPlan
+response = myTestLink.getTestPlanCustomFieldDesignValue(
+ 'cf_tp_string', args['testprojectid'], args['testplanid'])
+print( "getTestPlanCustomFieldDesignValue", response )
+
+# get CustomField Value - Requirement Specification
+response = myTestLink.getReqSpecCustomFieldDesignValue(
+ 'cf_req_sd_string', args['testprojectid'], args['reqspecid'])
+print( "getReqSpecCustomFieldDesignValue", response )
+
+
+# get CustomField Value - Requirement Specification
+response = myTestLink.getRequirementCustomFieldDesignValue(
+ 'cf_req_string',args['testprojectid'], args['requirementid'])
+print( "getRequirementCustomFieldDesignValue", response )
+
+# update CustomField Value - Build
+response = myTestLink.updateBuildCustomFieldsValues(
+ args['testprojectid'], args['testplanid'], args['buildid'],
+ {'cf_b_string' : 'A custom Build value set via api'})
+print( "updateBuildCustomFieldsValues", response )
+
+
diff --git a/example/all-req.xml b/example/all-req.xml
new file mode 100644
index 0000000..1a1b4f8
--- /dev/null
+++ b/example/all-req.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+a section
]]>
+
+
+
+
+ 1
+ 1
+ 1
+ a use case]]>
+
+
+
+
+
+
+
+
+ 1
+ 1
+ 2
+ a none functional requirement]]>
+
+
+
+
+
+
+ none-function-01
+ use-case-01
+ 2
+
+
+
+
+
+
+
+
+
+
+a user requirement]]>
+
+
+
+
+ 1
+ 1
+ 1
+ a restriction]]>
+
+
+
+
+
+
+
+
+ 1
+ 1
+ 2
+ a user gui]]>
+
+
+
+
+
+
+ system-func-01
+ user-gui-01
+ 2
+
+
+
+
+
+
+
+
+
+
+a system requirement]]>
+
+
+
+
+ 1
+ 1
+ 2
+ a system function]]>
+
+
+
+
+
+
+
+
+ 1
+ 1
+ 3
+ a feature]]>
+
+
+
+
+
+
+ system-func-01
+ user-gui-01
+ 2
+
+
diff --git a/example/customFields_ExampleDefs.xml b/example/customFields_ExampleDefs.xml
new file mode 100644
index 0000000..02ca574
--- /dev/null
+++ b/example/customFields_ExampleDefs.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/keywords_ExampleDefs.xml b/example/keywords_ExampleDefs.xml
new file mode 100644
index 0000000..6998acb
--- /dev/null
+++ b/example/keywords_ExampleDefs.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/robot/TestlinkAPILibrary.py b/robot/TestlinkAPILibrary.py
new file mode 100644
index 0000000..41f0f99
--- /dev/null
+++ b/robot/TestlinkAPILibrary.py
@@ -0,0 +1,80 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2014-2019 Luiko Czub
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+
+from testlink import TestlinkAPIGeneric, TestlinkAPIClient, TestLinkHelper
+
+
+class TestlinkAPILibrary(object):
+ """Robot Framework test library for testing TestLink-API-Python-client,
+ a Python XML-RPC client for TestLink.
+
+ Best way to configure SERVER_URL and DEVKEY is to define them via enviroment
+ TESTLINK_API_PYTHON_SERVER_URL and TESTLINK_API_PYTHON_DEVKEY from outside
+ """
+
+ ROBOT_LIBRARY_SCOPE = 'TEST_SUITE'
+
+ def __init__(self, client_class='TestlinkAPIClient', server_url=None, devkey=None ):
+ self._client_class = client_class
+ self._server_url = server_url
+ self._devkey = devkey
+ self.api_client = None
+
+ def create_api_client(self, client_class='TestlinkAPIClient',
+ server_url=None, devkey=None):
+ """Creates a new TestLink-API-Python-client (XMLRPC), using Python class
+ TestlinkAPIClient or TestlinkAPIGeneric.
+ """
+
+ a_server_url = server_url or self._server_url
+ a_devkey = devkey or self._devkey
+ a_helper = TestLinkHelper(a_server_url, a_devkey)
+
+ a_class_name = client_class or self._client_class
+ a_api_class = globals()[a_class_name]
+ self.api_client = a_helper.connect(a_api_class)
+ return self.api_client
+
+ def call_api_method(self, method_name, *args):
+ """Calls a TestLink API method and returns TestLinks server response.
+ """
+
+ # this is an extended version of the BuiltIn keyword "Call Method"
+
+ client = self.api_client
+ try:
+ method = getattr(client, method_name)
+ except AttributeError:
+ raise RuntimeError("TLAPI Client '%s' does not have a method '%s'"
+ % (client.__class__.__name__, method_name))
+
+ # split positional and optional args
+ # condition optional args: argname=argvalue
+ # FIXME LC: real ugly code
+ posargs=[]
+ optargs={}
+ for a_arg in args:
+ if '=' in a_arg:
+ l = a_arg.split('=')
+ optargs[l[0]]=l[1]
+ else:
+ posargs.append(a_arg)
+
+ return method(*posargs, **optargs)
diff --git a/robot/TestlinkSeLibExtension.py b/robot/TestlinkSeLibExtension.py
new file mode 100644
index 0000000..0f291f2
--- /dev/null
+++ b/robot/TestlinkSeLibExtension.py
@@ -0,0 +1,143 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2014-2019 Luiko Czub
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+from robot.libraries.BuiltIn import BuiltIn
+
+class TestlinkSeLibExtension(object):
+ """ Extension of Robot Framework Selenium2library to access a TestLink web
+ application to collect and verify test data for Testlink XMLRPC api tests.
+ """
+
+ ROBOT_LIBRARY_SCOPE = 'TEST_SUITE'
+
+ def __init__(self, url='http://demo.testlink.org/latest',
+ username='admin', password='admin' ):
+ self._get_selenium_lib_instance()
+ self._store_server_url(url)
+ self._store_user(username, password)
+
+ def _store_server_url(self, url):
+ self._server_url = url
+
+ def _store_user(self, username, password):
+ self._username = username
+ self._password = password
+
+ def _get_selenium_lib_instance(self):
+ """ Gets current running Selenium2Library instance and store it
+ Uses BuiltIn().get_library_instance(), which normally should work also
+ during __init__
+ - see https://code.google.com/p/robotframework/issues/detail?id=646
+ But RIDE makes problems - the RIDE logs shows a traceback
+ - AttributeError: 'NoneType' object has no attribute 'namespace'
+ after selecting a testsuite only for reading.
+ To avoid this traceback, we will catch it and do nothing.
+ """
+ try:
+ self._seleniumlib = BuiltIn().get_library_instance('Selenium2Library')
+ except AttributeError:
+ # we do nothing see method comment
+ pass
+
+ def login_TestLink(self):
+ """ Logs into the TestLink web app """
+ SelLib = self._seleniumlib
+ SelLib.location_should_contain('login.php')
+ SelLib.input_text('tl_login', self._username)
+ SelLib.input_text('tl_password', self._password)
+ SelLib.click_button('login_submit')
+
+ def logout_TestLink(self):
+ """ Logs out of the TestLink web app"""
+ SelLib = self._seleniumlib
+ self.click_titlebar_element('Logout', 'login_div')
+
+ def click_frame_element(self, frame_name, frame_elem, frame_unselect=True):
+ """ Select frame FRAME_NAME and clicks on element FRAME_ELEM.
+ Unselect this frame at the end, if FRAME_UNSELECT is True (default).
+ internal keyword for testing the TestLink web app which uses framesets
+ """
+ SelLib = self._seleniumlib
+ SelLib.select_frame(frame_name)
+ SelLib.click_element(frame_elem)
+ if frame_unselect is True:
+ SelLib.unselect_frame()
+
+ def click_titlebar_element(self, tbar_elem, expected_elem):
+ """ Clicks in TestLinks *titlebar* frame on image with title TBAR_ELEM.
+ Waits till _mainframe_ includes the element EXPECTED_ELEM. """
+ SelLib = self._seleniumlib
+ SelLib.log_location()
+ img_xpath = "xpath=//img[@title='%s']" % tbar_elem
+ self.click_frame_element('titlebar', img_xpath)
+ SelLib.wait_until_page_contains_element(expected_elem)
+
+ def click_desktop_link(self, desktop_link, expected_elem):
+ """ Opens TestLinks *Desktop* page and clicks on link DESKTOP_LINK.
+ Waits till _mainframe_ includes the element EXPECTED_ELEM. """
+ SelLib = self._seleniumlib
+ self.click_titlebar_element('Desktop', 'mainframe')
+ a_link='link=%s' % desktop_link
+ self.click_frame_element('name=mainframe', a_link)
+ self.wait_until_frame_contains_element('mainframe', expected_elem)
+
+ def wait_until_frame_contains_element(self, frame_name,frame_elem):
+ """ Waits till frame FRAME_NAME exists, select this frame and waits
+ again till the element FRAME_ELEM exists in it.
+ internal keyword for testing the TestLink web app which uses framesets
+ """
+ SelLib = self._seleniumlib
+ a_frame = 'name=%s' % frame_name
+ SelLib.wait_until_page_contains_element(a_frame)
+ SelLib.select_frame(frame_name)
+ SelLib.wait_until_page_contains_element(frame_elem)
+
+ def get_all_visible_projects_identifier(self):
+ """ Return lists with four different identifier for test projects,
+ visible in TestLinks *titlebar* selecting list *testproject*
+
+ 1. List with test projects internal ID
+ 2. list with test projects prefix
+ 3. list with test projects name
+ 4. list with selection list option text
+
+ All four lists have the same length and order equates to selecting list
+ *testproject* """
+
+ js_script = """
+ //var myform = document.getElementsByName("titlebar")[0].contentWindow.document.getElementsByName("testproject")[0]
+ var myform = document.getElementsByName("testproject")[0]
+ var myOptions = [].slice.call( myform.options );
+ var tp_ids = myOptions.map(function(a_opt) {return a_opt.value})
+ var tp_prefixs = myOptions.map(function(a_opt) {
+ var a_title = a_opt.title;
+ var a_split = a_title.indexOf(":");
+ return a_title.substring(0,a_split)})
+ var tp_names = myOptions.map(function(a_opt) {
+ var a_title = a_opt.title;
+ var a_split = a_title.indexOf(":");
+ return a_title.substring(a_split+1)})
+ var opt_texts = myOptions.map(function(a_opt) {return a_opt.text})
+ return [tp_ids, tp_prefixs, tp_names, opt_texts]
+ """
+ SelLib = self._seleniumlib
+ SelLib.select_frame('titlebar')
+ return SelLib.execute_javascript(js_script)
+
+
\ No newline at end of file
diff --git a/robot/tlapi.robot b/robot/tlapi.robot
new file mode 100644
index 0000000..069c8c1
--- /dev/null
+++ b/robot/tlapi.robot
@@ -0,0 +1,54 @@
+# Copyright 2014-2019 Luiko Czub
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation Smoke tests for Library TestlinkAPILibrary
+...
+... TestlinkAPILibrary is a wrapper for calling
+... Testlink XMLRPC api methods via *Testlink-API-Python-client*
+... classes _TestlinkAPIClient_ and _TestlinkAPIGeneric_.
+Force Tags api
+Default Tags general
+Library TestlinkAPILibrary
+Library Collections
+
+*** Variables ***
+
+*** Test Cases ***
+Init Api Class TestLinkAPIClient
+ ${tlapi_client}= Create Api Client TestlinkAPIClient
+ ${method_args}= Call Method ${tlapi_client} whatArgs repeat
+ Should Contain ${method_args} Repeats a message back
+ Should Be Equal As Strings ${tlapi_client.__class__.__name__} TestlinkAPIClient
+
+Init Api Class TestlinkAPIGeneric
+ ${tlapi_client}= Create Api Client TestlinkAPIGeneric
+ ${method_args}= Call Method ${tlapi_client} whatArgs repeat
+ Should Contain ${method_args} Repeats a message back
+ Should Be Equal As Strings ${tlapi_client.__class__.__name__} TestlinkAPIGeneric
+
+Call Api Method without arguments
+ ${response}= Call Api Method sayHello
+ Should Be Equal As Strings ${response} Hello!
+
+Call Api Method with mandatory argument
+ ${response}= Call Api Method repeat Hugo
+ Should Be Equal As Strings ${response} You said: Hugo
+
+Call Api Method with optional arguments
+ [Tags] devexample
+ @{response1}= Call Api Method getTestCaseIDByName TESTCASE_B
+ Should Not Be Empty ${response1}
+ @{response2}= Call Api Method getTestCaseIDByName TESTCASE_B testprojectname=PROJECT_API_GENERIC-10
+ Should Not Be Empty ${response2}
diff --git a/robot/tlweb.robot b/robot/tlweb.robot
new file mode 100644
index 0000000..f7d4900
--- /dev/null
+++ b/robot/tlweb.robot
@@ -0,0 +1,86 @@
+# Copyright 2014-2019 Luiko Czub
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation Smoke tests for Library TestlinkSeLibExtension
+...
+... Selenium2Library extension to collect and verify test data in
+... TestLink web applications for Testlink XMLRPC api tests.
+...
+... Login - Open Test Specification and Test Project Management pages - Logout
+...
+... pybot parameter for TestLink Demo applikation
+...
+... --variable SERVER:demo.testlink.org/latest --variable DELAY:0.2
+Suite Setup Open Browser with TestLink Page
+Suite Teardown Close Browser
+Test Setup Unselect Frame
+Force Tags web
+Default Tags general
+Resource tlweb_resource.robot
+
+*** Test Cases ***
+Login TestLink
+ [Documentation] Log into the TestLink web application can be done with
+ ... _TestlinkSeLibExtension_ keyword *Login TestLink*.
+ Login TestLink
+ Location Should Contain caller=login
+
+Click Titlebar Element - Test Specification
+ [Documentation] Open Test Specification page can be done with
+ ... _TestlinkSeLibExtension_ keyword *Click Titlebar Element*.
+ Click Titlebar Element Test Specification mainframe
+ Wait Until Frame Contains Element mainframe treeframe
+ Capture Page Screenshot
+
+Click Desktop Link - Test Project Management
+ [Documentation] Open Test Project Management page can be done with
+ ... _TestlinkSeLibExtension_ keyword *Click Titlebar Element*.
+ Click Desktop Link Test Project Management search
+ Capture Page Screenshot
+
+Get List with Identifier of All Visible Test Projects
+ [Documentation] List with Identifier of all visible Test Projects can be
+ ... created with _TestlinkSeLibExtension_ keyword
+ ... *Get All Visible Projects Identifier*.
+ @{tp_infos}= Get All Visible Projects Identifier
+ Length Should Be ${tp_infos} 4
+ ${tp_count}= Get Length @{tp_infos}[0]
+ Length Should Be @{tp_infos}[1] ${tp_count}
+ Length Should Be @{tp_infos}[2] ${tp_count}
+ Length Should Be @{tp_infos}[3] ${tp_count}
+
+Select TestProject
+ [Documentation] selects the second test project in titlebars project list
+ Select Frame titlebar
+ Log Source
+ Capture Page Screenshot
+ @{tprojects}= Get List Items testproject
+ Select From List By Index testproject 2
+ Capture Page Screenshot
+ Log Source
+ ${tp_value}= Execute Javascript return document.getElementsByName("testproject")[0].options[0].value
+ ${tp_title}= Execute Javascript return document.getElementsByName("testproject")[0].options[0].title
+
+Click Desktop Link - Test Specification
+ [Documentation] Open Test Specification page can be done with
+ ... _TestlinkSeLibExtension_ keyword *Click Desktop Link*.
+ Click Desktop Link Test Specification treeframe
+ Capture Page Screenshot
+
+Logout TestLink
+ [Documentation] Log out of the TestLink web application can be done with
+ ... _TestlinkSeLibExtension_ keyword *Logout TestLink*.
+ Logout TestLink
+ Location Should Contain login.php
diff --git a/robot/tlweb_resource.robot b/robot/tlweb_resource.robot
new file mode 100644
index 0000000..bdc7a8c
--- /dev/null
+++ b/robot/tlweb_resource.robot
@@ -0,0 +1,33 @@
+# Copyright 2014-2019 Luiko Czub
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation Resource file for testing TestLink XMLRPC api with web app interaction
+Library Selenium2Library
+Library TestlinkSeLibExtension ${TL_URL} ${TL_USER} ${TL_PASSWORD}
+
+*** Variables ***
+${SERVER} lkatlinkd7/testlink
+${BROWSER} ff
+${DELAY} 0.1
+${TL_USER} admin
+${TL_PASSWORD} admin
+${TL_URL} http://${SERVER}/
+
+*** Keywords ***
+Open Browser with TestLink Page
+ [Documentation] prepares the browser for testing the TestLink web app
+ Open Browser ${TL_URL} ${BROWSER}
+ Maximize Browser Window
+ Set Selenium Speed ${DELAY}
diff --git a/setup.py b/setup.py
index c5c25df..86ced60 100644
--- a/setup.py
+++ b/setup.py
@@ -1,24 +1,88 @@
#! /usr/bin/python
# -*- coding: UTF-8 -*-
-# Copyright 2012 ??? , ???
+# Copyright 2012-2019 Luiko Czub, TestLink-API-Python-client developers
#
-# Licensed under ???
+# Licensed under Apache 2.0
#
from os.path import join, dirname
from distutils.core import setup
-execfile(join(dirname(__file__), 'src', 'testlink', 'version.py'))
+with open(join(dirname(__file__), 'src', 'testlink', 'version.py')) as fpv:
+ version_code = compile(fpv.read(), 'version.py', 'exec')
+ exec(version_code)
-setup(name='TestLink',
+CLASSIFIERS = [
+ 'Development Status :: 5 - Production/Stable',
+ 'License :: OSI Approved :: Apache Software License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Topic :: Software Development :: Testing',
+ 'Topic :: Software Development :: Libraries :: Python Modules'
+]
+
+DESCRIPTION = """
+TestLink-API-Python-client is a Python XML-RPC client for TestLink_.
+
+Initially based on James Stock testlink-api-python-client R7 and Olivier
+Renault JinFeng_ idea - an interaction of TestLink_, `Robot Framework`_ and Jenkins_.
+
+TestLink-API-Python-client delivers two main classes
+
+- TestlinkAPIGeneric - Implements the TestLink API methods as generic PY methods
+ with error handling
+- TestlinkAPIClient - Inherits from TestlinkAPIGeneric and defines service
+ methods like "copyTCnewVersion".
+
+and the helper class
+
+- TestLinkHelper - search connection parameter from environment variables and
+ command line arguments
+
+How to talk with TestLink in a python shell and copy a test case: ::
+
+ set TESTLINK_API_PYTHON_SERVER_URL=http://[YOURSERVER]/testlink/lib/api/xmlrpc/v1/xmlrpc.php
+ set TESTLINK_API_PYTHON_DEVKEY=[Users devKey generated by TestLink]
+ python
+ >>> import testlink
+ >>> tls = testlink.TestLinkHelper().connect(testlink.TestlinkAPIClient)
+ >>> tls.countProjects()
+ 3
+ >>> tc_info = tls.getTestCase(None, testcaseexternalid='NPROAPI-3')
+ [{'full_tc_external_id': 'NPROAPI-3', ..., 'id': '5440', 'version': '2',
+ 'testsuite_id': '5415', 'tc_external_id': '3','testcase_id': '5425', ...}]
+ >>> tls.copyTCnewTestCase(tc_info[0]['testcase_id'], testsuiteid=newSuiteID,
+ testcasename='a new test case name')
+ >>> print tls.whatArgs('createTestPlan')
+ createTestPlan(, , [note=], [active=],
+ [public=], [devKey=])
+ create a test plan
+
+More information about this library can be found on the Wiki_
+
+.. _TestLink: http://testlink.org
+.. _JinFeng: http://www.sqaopen.net/blog/en/?p=63
+.. _Robot Framework: http://code.google.com/p/robotframework
+.. _Jenkins: http://jenkins-ci.org
+.. _Wiki: https://github.com/lczub/TestLink-API-Python-client/wiki
+
+"""[1:-1]
+
+setup(name='TestLink-API-Python-client',
version=VERSION,
- description='Python XMLRPC client for the TestLink API',
- author='James Stock, Olivier Renault, TestLink-API-Python-client developers',
- author_email='???, ???, ???',
+ description='Python XML-RPC client for TestLink %s' % TL_RELEASE,
+ long_description = DESCRIPTION,
+ author='James Stock, Olivier Renault, Luiko Czub, TestLink-API-Python-client developers',
+ author_email='orenault@gmail.com, Luiko.Czub@Liegkat-Archiv.de',
url='https://github.com/lczub/TestLink-API-Python-client',
- license = 'unknown',
+ license = 'Apache 2.0',
package_dir = {'': 'src'},
- packages=['testlink'],
+ packages = ['testlink'],
+ classifiers = CLASSIFIERS,
+ platforms = 'any',
+ keywords = ['testing', 'testlink', 'xml-rpc', 'testautomation']
+
)
-
diff --git a/src/testlink/__init__.py b/src/testlink/__init__.py
index f5d1028..7b5de11 100644
--- a/src/testlink/__init__.py
+++ b/src/testlink/__init__.py
@@ -1,7 +1,7 @@
#! /usr/bin/python
# -*- coding: UTF-8 -*-
-# Copyright 2012 TestLink-API-Python-client developers
+# Copyright 2012-2019 TestLink-API-Python-client developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@
from .testlinkerrors import TestLinkError
+from .testlinkapigeneric import TestlinkAPIGeneric
from .testlinkapi import TestlinkAPIClient
-from .testlink import TestLink
-from .testlinkhelper import TestLinkHelper
\ No newline at end of file
+from .testlinkhelper import TestLinkHelper
+from .testreporter import TestReporter
+from .testgenreporter import TestGenReporter
diff --git a/src/testlink/proxiedtransport2.py b/src/testlink/proxiedtransport2.py
new file mode 100644
index 0000000..dd7bf2c
--- /dev/null
+++ b/src/testlink/proxiedtransport2.py
@@ -0,0 +1,43 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2014-2020 Mario Benito, Luiko Czub, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+# XMLRPC ProxiedTransport for Py27 as described in
+# https://docs.python.org/2.7/library/xmlrpclib.html#example-of-client-usage
+# ------------------------------------------------------------------------
+
+import xmlrpclib, httplib
+
+class ProxiedTransport(xmlrpclib.Transport):
+ ''' XMLRPC ProxiedTransport for Py27 as described in
+ https://docs.python.org/2.7/library/xmlrpclib.html#example-of-client-usage
+ '''
+
+ def set_proxy(self, proxy):
+ self.proxy = proxy
+
+ def make_connection(self, host):
+ self.realhost = host
+ h = httplib.HTTPConnection(self.proxy)
+ return h
+
+ def send_request(self, connection, handler, request_body):
+ connection.putrequest("POST", 'http://%s%s' % (self.realhost, handler))
+
+ def send_host(self, connection, host):
+ connection.putheader('Host', self.realhost)
+
diff --git a/src/testlink/proxiedtransport3.py b/src/testlink/proxiedtransport3.py
new file mode 100644
index 0000000..e12ab77
--- /dev/null
+++ b/src/testlink/proxiedtransport3.py
@@ -0,0 +1,46 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2020 Luiko Czub, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+from http.client import HTTPConnection
+from xmlrpc.client import Transport
+from urllib.parse import urlparse, urlunparse
+
+class ProxiedTransport(Transport):
+ ''' XMLRPC ProxiedTransport for Py37+ as described in
+ https://docs.python.org/3.8/library/xmlrpc.client.html#example-of-client-usage
+ '''
+
+ def set_proxy(self, host, port=None, headers=None):
+ ''' if host includes a port definition (e.g. http://myHost:1111)
+ this will be used instead the optional PORT arg
+ '''
+
+ u1 = urlparse(host)
+ uport = u1.port
+ u2 = u1._replace(netloc=u1.hostname)
+ uhost = urlunparse(u2)
+
+ self.proxy = uhost, uport or port
+ self.proxy_headers = headers
+
+ def make_connection(self, host):
+ connection = HTTPConnection(*self.proxy)
+ connection.set_tunnel(host, headers=self.proxy_headers)
+ self._connection = host, connection
+ return connection
diff --git a/src/testlink/testgenreporter.py b/src/testlink/testgenreporter.py
new file mode 100644
index 0000000..d856dd2
--- /dev/null
+++ b/src/testlink/testgenreporter.py
@@ -0,0 +1,50 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2017-2019 Brian-Willams, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+from .testreporter import AddTestCaseReporter, AddBuildReporter, AddTestPlanReporter, AddPlatformReporter, TestReporter
+
+
+class TestGenReporter(AddTestCaseReporter, AddBuildReporter, AddTestPlanReporter, AddPlatformReporter, TestReporter):
+ """
+ This is the default generate everything it can version of test reporting.
+
+ This class will always try to report a result. It will generate everything possible and will change with additional
+ Add*Reporter's added to the repo. As such you should only use this if you want to always generate everything this
+ repo is capable of. If you want what it does at any specific time you should create this class in your project and
+ use directly.
+
+ If you don't want to generate one of these values you can 'roll your own' version of this class with only the
+ needed features that you want to generate. As stated above if you *only* want to generate what this class currently
+ does. Copying it into your project is the best practice as this class is mutable inside the project!
+
+ For example if you wanted to add platforms and/or tests to testplans, but didn't want to ever make a new testplan
+ you could use a class like:
+ `type('MyOrgTestGenReporter', (AddTestCaseReporter, AddPlatformReporter, TestReporter), {})`
+
+ Example usage with fake testlink server test and a manual project.
+ ```
+ tls = testlink.TestLinkHelper('https://testlink.corp.com/testlink/lib/api/xmlrpc/v1/xmlrpc.php',
+ 'devkeyabc123').connect(testlink.TestlinkAPIClient)
+ tgr = TestGenReporter(tls, ['TEST-123'], testprojectname='MANUALLY_MADE_PROJECT', testplanname='generated',
+ platformname='gend', buildname='8.fake', status='p')
+ tgr.report()
+ ```
+
+ Attention - the list of test case IDs must use full external testcase IDs
+ """
diff --git a/src/testlink/testlink.py b/src/testlink/testlink.py
deleted file mode 100644
index 28a2243..0000000
--- a/src/testlink/testlink.py
+++ /dev/null
@@ -1,170 +0,0 @@
-#! /usr/bin/python
-# -*- coding: UTF-8 -*-
-
-# Copyright 2012 pade (Patrick Dassier), TestLink-API-Python-client developers
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# ------------------------------------------------------------------------
-
-from testlinkapi import TestlinkAPIClient, TestLinkHelper
-from testlinkerrors import TestLinkError
-from datetime import date
-
-
-class TestLink(TestlinkAPIClient):
- """
- TestLink API library
- provide a user friendly library, with more robustness and error management
- """
-
- def __init__(self, server_url, key):
- """
- Class initialisation
- """
- super(TestLink, self).__init__(server_url, key)
-
- def getTestCaseIDByName(self, testCaseName, testSuiteName, testProjectName):
- """
- Find a test case by its name, by its suite and its project
- Suite name must not be duplicate, so only one test case must be found
- Return test case id if success
- or raise TestLinkError exception with error message in case of error
- """
- results = super(TestLink, self).getTestCaseIDByName(testCaseName, testSuiteName, testProjectName)
- if results[0].has_key("message"):
- raise TestLinkError(results[0]["message"])
- elif len(results) > 1:
- raise TestLinkError("(getTestCaseIDByName) - Several case test found. Suite name must not be duplicate for the same project")
- else:
- if results[0]["name"] == testCaseName:
- return results[0]["id"]
- raise TestLinkError("(getTestCaseIDByName) - Internal server error. Return value is not expected one!")
-
-
- def reportResult(self, testResult, testCaseName, testSuiteName, testNotes="", **kwargs):
- """
- Report results for test case
- Arguments are:
- - testResult: "p" for passed, "b" for blocked, "f" for failed
- - testCaseName: the test case name to report
- - testSuiteName: the test suite name that support the test case
- - testNotes: optional, if empty will be replace by a default string. To let it blank, just set testNotes to " " characters
- - an anonymous dictionnary with followings keys:
- - testProjectName: the project to fill
- - testPlanName: the active test plan
- - buildName: the active build.
- Raise a TestLinkError error with the error message in case of trouble
- Return the execution id needs to attach files to test execution
- """
-
- # Check parameters
- for data in ["testProjectName", "testPlanName", "buildName"]:
- if not kwargs.has_key(data):
- raise TestLinkError("(reportResult) - Missing key %s in anonymous dictionnary" % data)
-
- # Get project id
- project = self.getTestProjectByName(kwargs["testProjectName"])
-
- # Check if project is active
- if project['active'] != '1':
- raise TestLinkError("(reportResult) - Test project %s is not active" % kwargs["testProjectName"])
-
- # Check test plan name
- plan = self.getTestPlanByName(kwargs["testProjectName"], kwargs["testPlanName"])
-
- # Check is test plan is open and active
- if plan['is_open'] != '1' or plan['active'] != '1':
- raise TestLinkError("(reportResult) - Test plan %s is not active or not open" % kwargs["testPlanName"])
- # Memorise test plan id
- planId = plan['id']
-
- # Check build name
- build = self.getBuildByName(kwargs["testProjectName"], kwargs["testPlanName"], kwargs["buildName"])
-
- # Check if build is open and active
- if build['is_open'] != '1' or build['active'] != '1':
- raise TestLinkError("(reportResult) - Build %s in not active or not open" % kwargs["buildName"])
-
- # Get test case id
- caseId = self.getTestCaseIDByName(testCaseName, testSuiteName, kwargs["testProjectName"])
-
- # Check results parameters
- if testResult not in "pbf":
- raise TestLinkError("(reportResult) - Test result must be 'p', 'f' or 'b'")
-
- if testNotes == "":
- # Builds testNotes if empty
- today = date.today()
- testNotes = "%s - Test performed automatically" % today.strftime("%c")
- elif testNotes == " ":
- #No notes
- testNotes = ""
-
- print "testNotes: %s" % testNotes
- # Now report results
- results = self.reportTCResult(caseId, planId, kwargs["buildName"], testResult, testNotes)
- # Check errors
- if results[0]["message"] != "Success!":
- raise TestLinkError(results[0]["message"])
-
- return results[0]['id']
-
- def getTestProjectByName(self, testProjectName):
- """
- Return project
- A TestLinkError is raised in case of error
- """
- results = super(TestLink, self).getTestProjectByName(testProjectName)
- if results[0].has_key("message"):
- raise TestLinkError(results[0]["message"])
-
- return results[0]
-
- def getTestPlanByName(self, testProjectName, testPlanName):
- """
- Return test plan
- A TestLinkError is raised in case of error
- """
- results = super(TestLink, self).getTestPlanByName(testProjectName, testPlanName)
- if results[0].has_key("message"):
- raise TestLinkError(results[0]["message"])
-
- return results[0]
-
- def getBuildByName(self, testProjectName, testPlanName, buildName):
- """
- Return build corresponding to buildName
- A TestLinkError is raised in case of error
- """
- plan = self.getTestPlanByName(testProjectName, testPlanName)
- builds = self.getBuildsForTestPlan(plan['id'])
-
- # Check if a builds exists
- if builds == '':
- raise TestLinkError("(getBuildByName) - Builds %s does not exists for test plan %s" % (buildName, testPlanName))
-
- # Search the correct build name in the return builds list
- for build in builds:
- if build['name'] == buildName:
- return build
-
- # No build found with builName name
- raise TestLinkError("(getBuildByName) - Builds %s does not exists for test plan %s" % (buildName, testPlanName))
-
-if __name__ == "__main__":
- tl_helper = TestLinkHelper()
- tl_helper.setParamsFromArgs()
- myTestLink = tl_helper.connect(TestLink)
- print myTestLink
- print myTestLink.about()
diff --git a/src/testlink/testlinkapi.py b/src/testlink/testlinkapi.py
index 580f6c8..0ca7e14 100644
--- a/src/testlink/testlinkapi.py
+++ b/src/testlink/testlinkapi.py
@@ -1,7 +1,7 @@
#! /usr/bin/python
# -*- coding: UTF-8 -*-
-# Copyright 2011-2012 Olivier Renault, James Stock, TestLink-API-Python-client developers
+# Copyright 2011-2019 Luiko Czub, Olivier Renault, James Stock, TestLink-API-Python-client developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,418 +17,370 @@
#
# ------------------------------------------------------------------------
-import xmlrpclib
+#import xmlrpclib
-from testlinkhelper import TestLinkHelper, VERSION
-import testlinkerrors
+from __future__ import print_function
+from .testlinkapigeneric import TestlinkAPIGeneric, TestLinkHelper
+from .testlinkerrors import TLArgError, TLResponseError
+import sys
-class TestlinkAPIClient(object):
+class TestlinkAPIClient(TestlinkAPIGeneric):
+ """ client for XML-RPC communication between Python and TestLink
- __slots__ = ['server', 'devKey', 'stepsList', '_server_url']
-
- __VERSION__ = VERSION
-
- def __init__(self, server_url, devKey):
- self.server = xmlrpclib.Server(server_url)
- self.devKey = devKey
- self.stepsList = []
- self._server_url = server_url
+ Inherits TestLink API methods from the generic client TestlinkAPIGeneric.
- def _callServer(self, methodAPI, argsAPI=None):
- """ call server method METHODAPI with error handling and returns the
- responds """
+ Defines Service Methods like "countProjects" and change the
+ configuration for positional and optional arguments in a way, that often
+ used arguments are positional.
+ - see _changePositionalArgConfig()
+ - configuration of positional arguments is consistent with v0.4.0
+
+ Changes on Service Methods like "countProjects" should be implemented in
+ this class or sub classes
+ Changes of TestLink API methods should be implemented in generic API
+ TestlinkAPIGeneric.
+ """
+
+ __slots__ = ['stepsList']
+ __author__ = 'Luiko Czub, Olivier Renault, James Stock, TestLink-API-Python-client developers'
+
+ def __init__(self, server_url, devKey, **kwargs):
+ """ call super for init generell slots, init sepcial slots for teststeps
+ and define special positional arg settings """
+
+ kwargs['allow_none'] = True
+ super(TestlinkAPIClient, self).__init__(server_url, devKey, **kwargs)
+ # allow_none is an argument from xmlrpclib.Server()
+ # with set to True, it is possible to set positional args to None, so
+ # alternative optional arguments could be set
+ # example - testcaseid is set :
+ # reportTCResult(None, newTestPlanID, None, 'f', '', guess=True,
+ # testcaseexternalid=tc_aa_full_ext_id)
+ # otherwise xmlrpclib raise an error, that None values are not allowed
+ self._emptyStepsList()
+ self._changePositionalArgConfig()
+
+ def _changePositionalArgConfig(self):
+ """ set special positional arg configuration, which differs from the
+ generic configuration """
+ pos_arg_config = self._positionalArgNames
+
+ # createTestCases sets argument 'steps' with values from .stepsList
+ # - user must not passed a separate stepList
+ pos_arg_config['createTestCase'] = ['testcasename', 'testsuiteid',
+ 'testprojectid', 'authorlogin', 'summary'] #, 'steps']
+ # getTestCase
+ pos_arg_config['getTestCase'] = ['testcaseid']
+ # createVuild
+ pos_arg_config['createBuild'] = ['testplanid', 'buildname', 'buildnotes']
+ # reportTCResult
+ pos_arg_config['reportTCResult'] = ['testcaseid', 'testplanid',
+ 'buildname', 'status', 'notes']
+ # uploadExecutionAttachment
+ pos_arg_config['uploadExecutionAttachment'] = ['executionid', 'title',
+ 'description']
+ # getTestCasesForTestSuite
+ pos_arg_config['getTestCasesForTestSuite'] = ['testsuiteid', 'deep',
+ 'details']
+ # getLastExecutionResult
+ pos_arg_config['getLastExecutionResult'] = ['testplanid', 'testcaseid']
+ # getTestCaseCustomFieldDesignValue
+ pos_arg_config['getTestCaseCustomFieldDesignValue'] = [
+ 'testcaseexternalid', 'version' , 'testprojectid',
+ 'customfieldname', 'details']
+ # getTestCaseAttachments
+ pos_arg_config['getTestCaseAttachments'] = ['testcaseid']
- response = None
- try:
- if argsAPI is None:
- response = getattr(self.server.tl, methodAPI)()
- else:
- response = getattr(self.server.tl, methodAPI)(argsAPI)
- except (IOError, xmlrpclib.ProtocolError), msg:
- new_msg = 'problems connecting the TestLink Server %s\n%s' %\
- (self._server_url, msg)
- raise testlinkerrors.TLConnectionError(new_msg)
- except xmlrpclib.Fault, msg:
- new_msg = 'problems calling the API method %s\n%s' %\
- (methodAPI, msg)
- raise testlinkerrors.TLAPIError(new_msg)
-
- return response
#
- # BUILT-IN API CALLS
+ # BUILT-IN API CALLS - extented / customised against generic behaviour
#
- def checkDevKey(self):
- """ checkDevKey :
- check if Developer Key exists
- """
- argsAPI = {'devKey' : self.devKey}
- return self._callServer('checkDevKey', argsAPI)
-
- def about(self):
- """ about :
- Gives basic information about the API
- """
- return self._callServer('about')
-
- def ping(self):
- """ ping :
- """
- return self._callServer('ping')
-
def echo(self, message):
- return self._callServer('repeat', {'str': message})
+ return self.repeat(message)
- def doesUserExist(self, user):
- """ doesUserExist :
- Checks if a user name exists
- """
- argsAPI = {'devKey' : self.devKey,
- 'user':str(user)}
- return self._callServer('doesUserExist', argsAPI)
+ def getTestCaseIDByName(self, *argsPositional, **argsOptional):
+ """ getTestCaseIDByName : Find a test case by its name
+ positional args: testcasename,
+ optional args : testsuitename, testprojectname, testcasepathname
- def getBuildsForTestPlan(self, testplanid):
- """ getBuildsForTestPlan :
- Gets a list of builds within a test plan
- """
- argsAPI = {'devKey' : self.devKey,
- 'testplanid':str(testplanid)}
- return self._callServer('getBuildsForTestPlan', argsAPI)
-
- def getFirstLevelTestSuitesForTestProject(self,testprojectid):
- """ getFirstLevelTestSuitesForTestProject :
- Get set of test suites AT TOP LEVEL of tree on a Test Project
- """
- argsAPI = {'devKey' : self.devKey,
- 'testprojectid':str(testprojectid)}
- return self._callServer('getFirstLevelTestSuitesForTestProject', argsAPI)
-
- def getFullPath(self,nodeid):
- """ getFullPath :
- Gets full path from the given node till the top using
- nodes_hierarchy_table
- """
- argsAPI = {'devKey' : self.devKey,
- 'nodeid':str(nodeid)}
- return self._callServer('getFullPath', argsAPI)
-
- def getLastExecutionResult(self, testplanid, testcaseid):
- """ getLastExecutionResult :
- Gets the result of LAST EXECUTION for a particular testcase on a
- test plan, but WITHOUT checking for a particular build
- """
- argsAPI = {'devKey' : self.devKey,
- 'testplanid' : str(testplanid),
- 'testcaseid' : str(testcaseid)}
- return self._callServer('getLastExecutionResult', argsAPI)
-
- def getLatestBuildForTestPlan(self, testplanid):
- """ getLastExecutionResult :
- Gets the latest build by choosing the maximum build id for a
- specific test plan
- """
- argsAPI = {'devKey' : self.devKey,
- 'testplanid':str(testplanid)}
- return self._callServer('getLatestBuildForTestPlan', argsAPI)
+ testcasepathname : Full test case path name,
+ starts with test project name , pieces separator -> ::
+
+ server return can be a list or a dictionary
+ - optional arg testprojectname seems to create a dictionary response
+
+ this methods customize the generic behaviour and converts a dictionary
+ response into a list, so methods return will be always a list """
+
+ response = super(TestlinkAPIClient, self).getTestCaseIDByName(
+ *argsPositional, **argsOptional)
+ if type(response) == dict:
+ # convert dict into list - just use dicts values
+ response = list(response.values())
+ return response
- def getProjects(self):
- """ getProjects:
- Gets a list of all projects
- """
- argsAPI = {'devKey' : self.devKey}
- return self._callServer('getProjects', argsAPI)
+ def createTestCase(self, *argsPositional, **argsOptional):
+ """ createTestCase: Create a test case
+ positional args: testcasename, testsuiteid, testprojectid, authorlogin,
+ summary
+ optional args : steps, preconditions, importance, executiontype, order,
+ internalid, checkduplicatedname, actiononduplicatedname,
+ status, estimatedexecduration
+
+ argument 'steps' will be set with values from .stepsList,
+ - when argsOptional does not include a 'steps' item
+ - .stepsList can be filled before call via .initStep() and .appendStep()
+
+ otherwise, optional arg 'steps' must be defined as a list with
+ dictionaries , example
+ [{'step_number' : 1, 'actions' : "action A" ,
+ 'expected_results' : "result A", 'execution_type' : 0},
+ {'step_number' : 2, 'actions' : "action B" ,
+ 'expected_results' : "result B", 'execution_type' : 1},
+ {'step_number' : 3, 'actions' : "action C" ,
+ 'expected_results' : "result C", 'execution_type' : 0}]
- def getProjectTestPlans(self, testprojectid):
- """ getLastExecutionResult :
- Gets a list of test plans within a project
"""
- argsAPI = {'devKey' : self.devKey,
- 'testprojectid':str(testprojectid)}
- return self._callServer('getProjectTestPlans', argsAPI)
+
+ # store current stepsList as argument 'steps', when argsOptional defines
+ # no own 'steps' item
+ if self.stepsList:
+ if 'steps' in argsOptional:
+ raise TLArgError('confusing createTestCase arguments - ' +
+ '.stepsList and method args define steps')
+ argsOptional['steps'] = self.stepsList
+ self.stepsList = []
+ return super(TestlinkAPIClient, self).createTestCase(*argsPositional,
+ **argsOptional)
+
+ #
+ # ADDITIONNAL FUNCTIONS- copy test cases
+ #
- def getTestCase(self, testcaseid):
- """ getTestCase :
- Gets test case specification using external or internal id
+ def getProjectIDByNode(self, a_nodeid):
+ """ returns project id , the nodeid belongs to."""
+
+ # get node path
+ node_path = self.getFullPath(int(a_nodeid))[a_nodeid]
+ # get project and id
+ a_project = self.getTestProjectByName(node_path[0])
+ return a_project['id']
+
+ def copyTCnewVersion(self, origTestCaseId, origVersion=None, **changedAttributes):
+ """ creates a new version for test case ORIGTESTCASEID
+
+ ORIGVERSION specifies the test case version, which should be copied,
+ default is the max version number
+
+ if the new version should differ from the original test case, changed
+ api arguments could be defined as key value pairs.
+ Example for changed summary and importance:
+ - copyTCnewVersion('4711', summary = 'The summary has changed',
+ importance = '1')
+ Remarks for some special keys:
+ 'steps': must be a complete list of all steps, changed and unchanged steps
+ Maybe its better to change the steps in a separat call using
+ createTestCaseSteps with action='update'.
"""
- argsAPI = {'devKey' : self.devKey,
- 'testcaseid' : str(testcaseid)}
- return self._callServer('getTestCase', argsAPI)
-
- def getTestCaseAttachments(self, testcaseid):
- """ getTestCaseAttachments :
- Gets attachments for specified test case
+
+ return self._copyTC(origTestCaseId, changedAttributes, origVersion,
+ duplicateaction = 'create_new_version')
+
+ def copyTCnewTestCase(self, origTestCaseId, origVersion=None, **changedAttributes):
+ """ creates a test case with values from test case ORIGTESTCASEID
+
+ ORIGVERSION specifies the test case version, which should be copied,
+ default is the max version number
+
+ if the new test case should differ from the original test case, changed
+ api arguments could be defined as key value pairs.
+ Example for changed test suite and importance:
+ - copyTCnewTestCaseVersion('4711', testsuiteid = '1007',
+ importance = '1')
+
+ Remarks for some special keys:
+ 'testsuiteid': defines, in which test suite the TC-copy is inserted.
+ Default is the same test suite as the original test case.
+ 'steps': must be a complete list of all steps, changed and unchanged steps
+ Maybe its better to change the steps in a separat call using
+ createTestCaseSteps with action='update'.
+
"""
- argsAPI = {'devKey' : self.devKey,
- 'testcaseid':str(testcaseid)}
- return self._callServer('getTestCaseAttachments', argsAPI)
-
- def getTestCaseCustomFieldDesignValue(self, testcaseexternalid, version,
- testprojectid, customfieldname, details):
- """ getTestCaseCustomFieldDesignValue :
- Gets value of a Custom Field with scope='design' for a given Test case
+
+ return self._copyTC(origTestCaseId, changedAttributes, origVersion,
+ duplicateaction = 'generate_new')
+
+ def getTestCaseByVersion(self, testCaseID, version=None):
"""
- argsAPI = {'devKey' : self.devKey,
- 'testcaseexternalid' : str(testcaseexternalid),
- 'version' : str(version),
- 'testprojectid' : str(testprojectid),
- 'customfieldname' : str(customfieldname),
- 'details' : str(details)}
- return self._callServer('getTestCaseCustomFieldDesignValue', argsAPI)
+ Gets testcase information based on the version.
- def getTestCaseIDByName(self, testCaseName, testSuiteName=None, testProjectName=None):
- """
- Find a test case by its name
- testSuiteName and testProjectName are optionals arguments
- This function return a list of tests cases
+ :param testCaseID: test case to search for
+ :param version: version to search for defaults to None. None searches for latest
+ :return: test case info dictionary
"""
- argsAPI = {'devKey' : self.devKey,
- 'testcasename':str(testCaseName)}
-
- if testSuiteName is not None:
- argsAPI.update({'testsuitename':str(testSuiteName)})
-
- if testProjectName is not None:
- argsAPI.update({'testprojectname':str(testProjectName)})
-
- # Server return can be a list or a dictionnary !
- # This function always return a list
- ret_srv = self._callServer('getTestCaseIDByName', argsAPI)
- if type(ret_srv) == dict:
- retval = []
- for value in ret_srv.values():
- retval.append(value)
- return retval
+ testcases = self.getTestCase(testCaseID, version=version)
+ if version is None:
+ return testcases[0]
+ for testcase in testcases:
+ if str(testcase['version']) == str(version):
+ return testcase
else:
- return ret_srv
+ raise TLArgError("Testcase {} doesn't have version {}.".format(testCaseID, version))
- def getTestCasesForTestPlan(self, *args):
- """ getTestCasesForTestPlan :
- List test cases linked to a test plan
- Mandatory parameters : testplanid
- Optional parameters : testcaseid, buildid, keywordid, keywords,
- executed, assignedto, executestatus, executiontype, getstepinfo
- """
- testplanid = args[0]
- argsAPI = {'devKey' : self.devKey,
- 'testplanid' : str(testplanid)}
- if len(args)>1:
- params = args[1:]
- for param in params:
- paramlist = param.split("=")
- argsAPI[paramlist[0]] = paramlist[1]
- return self._callServer('getTestCasesForTestPlan', argsAPI)
-
- def getTestCasesForTestSuite(self, testsuiteid, deep, details):
- """ getTestCasesForTestSuite :
- List test cases within a test suite
- """
- argsAPI = {'devKey' : self.devKey,
- 'testsuiteid' : str(testsuiteid),
- 'deep' : str(deep),
- 'details' : str(details)}
- return self._callServer('getTestCasesForTestSuite', argsAPI)
-
- def getTestPlanByName(self, testprojectname, testplanname):
- """ getTestPlanByName :
- Gets info about target test project
- """
- argsAPI = {'devKey' : self.devKey,
- 'testprojectname' : str(testprojectname),
- 'testplanname' : str(testplanname)}
- return self._callServer('getTestPlanByName', argsAPI)
+ def _copyTC(self, origTestCaseId, changedArgs, origVersion=None, **options):
+ """ creates a copy of test case with id ORIGTESTCASEID
+
+ returns createTestCase response for the copy
+
+ CHANGEDARGUMENTS defines a dictionary with api arguments, expected from
+ createTestCase. Only arguments, which differ between TC-orig
+ and TC-copy must be defined
+ Remarks for some special keys:
+ 'testsuiteid': defines, in which test suite the TC-copy is inserted.
+ Default is the same test suite as the original test case.
+ 'steps': must be a complete list of all steps, changed and unchanged steps
+ Maybe its better to change the steps in a separat call using
+ createTestCaseSteps with action='update'.
+
+ ORIGVERSION specifies the test case version, which should be copied,
+ default is the max version number
- def getTestPlanPlatforms(self, testplanid):
- """ getTestPlanPlatforms :
- Returns the list of platforms associated to a given test plan
+ OPTIONS are optional key value pairs to influence the copy process
+ - details see comments _copyTCbuildArgs()
+
"""
- argsAPI = {'devKey' : self.devKey,
- 'testplanid' : str(testplanid)}
- return self._callServer('getTestPlanPlatforms', argsAPI)
- def getTestProjectByName(self, testprojectname):
- """ getTestProjectByName :
- Gets info about target test project
- """
- argsAPI = {'devKey' : self.devKey,
- 'testprojectname' : str(testprojectname)}
- return self._callServer('getTestProjectByName', argsAPI)
-
- def getTestSuiteByID(self, testsuiteid):
- """ getTestSuiteByID :
- Return a TestSuite by ID
- """
- argsAPI = {'devKey' : self.devKey,
- 'testsuiteid' : str(testsuiteid)}
- return self._callServer('getTestSuiteByID', argsAPI)
-
- def getTestSuitesForTestPlan(self, testplanid):
- """ getTestSuitesForTestPlan :
- List test suites within a test plan alphabetically
- """
- argsAPI = {'devKey' : self.devKey,
- 'testplanid' : str(testplanid)}
- return self._callServer('getTestSuitesForTestPlan', argsAPI)
+ # get orig test case content
+ origArgItems = self.getTestCaseByVersion(origTestCaseId, version=origVersion)
+ # get orig test case project id
+ origArgItems['testprojectid'] = self.getProjectIDByNode(origTestCaseId)
+
+ # build args for the TC-copy
+ (posArgValues, newArgItems) = self._copyTCbuildArgs(origArgItems,
+ changedArgs, options)
+ # create the TC-Copy
+ response = self.createTestCase(*posArgValues, **newArgItems)
+ return response
- def getTestSuitesForTestSuite(self, testsuiteid):
- """ getTestSuitesForTestSuite :
- get list of TestSuites which are DIRECT children of a given TestSuite
- """
- argsAPI = {'devKey' : self.devKey,
- 'testsuiteid' : str(testsuiteid)}
- return self._callServer('getTestSuitesForTestSuite', argsAPI)
+ def _copyTCbuildArgs(self, origArgItems, changedArgs, options):
+ """ build Args to create a new test case .
+ ORIGARGITEMS is a dictionary with getTestCase response of an existing
+ test case
+ CHANGEDARGS is a dictionary with api argument for createTestCase, which
+ should differ from these
+ OPTIONS is a dictionary with settings for the copy process
+
+ 'duplicateaction': decides, how the TC-copy is inserted
+ - 'generate_new' (default): a separate new test case is created, even
+ if name and test suite are equal
+ - 'create_new_version': if the target test suite includes already a
+ test case with the same name, a new version is created.
+ if the target test suite includes not a test case with the
+ defined name, a new test case with version 1 is created
+ """
+
+ # collect info, which arguments createTestCase expects
+ (posArgNames, optArgNames, manArgNames) = \
+ self._apiMethodArgNames('createTestCase')
+ # some argNames not realy needed
+ optArgNames.remove('internalid')
+ optArgNames.remove('devKey')
+
+ # mapping between getTestCase response and createTestCase arg names
+ externalArgNames = posArgNames[:]
+ externalArgNames.extend(optArgNames)
+ externalTointernalNames = {'testcasename' : 'name',
+ 'testsuiteid' : 'testsuite_id', 'authorlogin' : 'author_login',
+ 'executiontype' : 'execution_type', 'order' : 'node_order',
+ 'estimatedexecduration' : 'estimated_exec_duration' }
- def getTotalsForTestPlan(self, testplanid):
- """ getTotalsForTestPlan :
- Gets the summarized results grouped by platform
- """
- argsAPI = {'devKey' : self.devKey,
- 'testplanid' : str(testplanid)}
- return self._callServer('getTotalsForTestPlan', argsAPI)
-
- def createTestProject(self, *args):
- """ createTestProject :
- Create a test project
- Mandatory parameters : testprojectname, testcaseprefix
- Optional parameters : notes, options, active, public
- Options: map of requirementsEnabled, testPriorityEnabled,
- automationEnabled, inventoryEnabled
- """
- testprojectname = args[0]
- testcaseprefix = args[1]
- options={}
- argsAPI = {'devKey' : self.devKey,
- 'testprojectname' : str(testprojectname),
- 'testcaseprefix' : str(testcaseprefix)}
- if len(args)>2:
- params = args[2:]
- for param in params:
- paramlist = param.split("=")
- if paramlist[0] == "options":
- optionlist = paramlist[1].split(",")
- for option in optionlist:
- optiontuple = option.split(":")
- options[optiontuple[0]] = optiontuple[1]
- argsAPI[paramlist[0]] = options
- else:
- argsAPI[paramlist[0]] = paramlist[1]
- return self._callServer('createTestProject', argsAPI)
-
- def createBuild(self, testplanid, buildname, buildnotes):
- """ createBuild :
- Creates a new build for a specific test plan
- """
- argsAPI = {'devKey' : self.devKey,
- 'testplanid' : str(testplanid),
- 'buildname' : str(buildname),
- 'buildnotes' : str(buildnotes)}
- return self._callServer('createBuild', argsAPI)
-
- def createTestPlan(self, *args):
- """ createTestPlan :
- Create a test plan
- Mandatory parameters : testplanname, testprojectname
- Optional parameters : notes, active, public
- """
- testplanname = args[0]
- testprojectname = args[1]
- argsAPI = {'devKey' : self.devKey,
- 'testplanname' : str(testplanname),
- 'testprojectname' : str(testprojectname)}
- if len(args)>2:
- params = args[2:]
- for param in params:
- paramlist = param.split("=")
- argsAPI[paramlist[0]] = paramlist[1]
- return self._callServer('createTestPlan', argsAPI)
-
- def createTestSuite(self, *args):
- """ createTestSuite :
- Create a test suite
- Mandatory parameters : testprojectid, testsuitename, details
- Optional parameters : parentid, order, checkduplicatedname,
- actiononduplicatedname
- """
- argsAPI = {'devKey' : self.devKey,
- 'testprojectid' : str(args[0]),
- 'testsuitename' : str(args[1]),
- 'details' : str(args[2])}
- if len(args)>3:
- params = args[3:]
- for param in params:
- paramlist = param.split("=")
- argsAPI[paramlist[0]] = paramlist[1]
- return self._callServer('createTestSuite', argsAPI)
+ # extend origItems with some values needed in createTestCase
+ origArgItems['checkduplicatedname'] = 1
+ origArgItems['actiononduplicatedname'] = options.get('duplicateaction',
+ 'generate_new')
+ # build arg dictionary for TC-copy with orig values
+ newArgItems = {}
+ for exArgName in externalArgNames:
+ inArgName = externalTointernalNames.get(exArgName, exArgName)
+ newArgItems[exArgName] = origArgItems[inArgName]
+
+ # if changed values defines a different test suite, add the correct
+ # project id
+ if 'testsuiteid' in changedArgs:
+ changedProjID = self.getProjectIDByNode(changedArgs['testsuiteid'])
+ changedArgs['testprojectid'] = changedProjID
+
+ # change orig values for TC-copy
+ for (argName, argValue) in list(changedArgs.items()):
+ newArgItems[argName] = argValue
+
+ # separate positional and optional createTestCase arguments
+ posArgValues = []
+ for argName in posArgNames:
+ posArgValues.append(newArgItems[argName])
+ newArgItems.pop(argName)
+
+ return (posArgValues, newArgItems)
- def createTestCase(self, *args):
- """ createTestCase :
- Create a test case
- Mandatory parameters : testcasename, testsuiteid, testprojectid,
- authorlogin, summary, steps
- Optional parameters : preconditions, importance, executiontype, order,
- internalid, checkduplicatedname, actiononduplicatedname
- """
- argsAPI = {'devKey' : self.devKey,
- 'testcasename' : str(args[0]),
- 'testsuiteid' : str(args[1]),
- 'testprojectid' : str(args[2]),
- 'authorlogin' : str(args[3]),
- 'summary' : str(args[4]),
- 'steps' : self.stepsList}
- if len(args)>5:
- params = args[5:]
- for param in params:
- paramlist = param.split("=")
- argsAPI[paramlist[0]] = paramlist[1]
- ret = self._callServer('createTestCase', argsAPI)
- self.stepsList = []
- return ret
+ #
+ # ADDITIONNAL FUNCTIONS- keywords
+ #
- def reportTCResult(self, testcaseid, testplanid, buildname, status, notes ):
+ def listKeywordsForTC(self, internal_or_external_tc_id):
+ """ Returns list with keyword for a test case
+ INTERNAL_OR_EXTERNAL_TC_ID defines
+ - either the internal test case ID (8111 or '8111')
+ - or the full external test case ID ('NPROAPI-2')
+
+ Attention:
+ - the tcversion_id is not supported
+ - it is not possible to ask for a special test case version, cause TL
+ links keywords against a test case and not a test case version
"""
- Report execution result
- testcaseid: internal testlink id of the test case
- testplanid: testplan associated with the test case
- buildname: build name of the test case
- status: test verdict ('p': pass,'f': fail,'b': blocked)
+
+ #ToDo LC 12.01.15 - simplify code with TL 1.9.13 api getTestCaseKeywords
+ # - indirect search via test suite and getTestCasesForTestSuite() isn't
+ # necessary any more
+ # - see enhancement issue #45
+
+ a_tc_id = str(internal_or_external_tc_id)
+
+ if '-' in a_tc_id:
+ # full external ID like 'NPROAPI-2', but we need the internal
+ a_tc = self.getTestCase(None, testcaseexternalid=a_tc_id )[0]
+ a_tc_id = a_tc['testcase_id']
- Return : [{'status': True, 'operation': 'reportTCResult', 'message': 'Success!', 'overwrite': False, 'id': '37'}]
- id correspond to the executionID needed to attach files to a test execution
- """
- argsAPI = {'devKey' : self.devKey,
- 'testcaseid' : testcaseid,
- 'testplanid' : testplanid,
- 'status': status,
- 'buildname': buildname,
- 'notes': str(notes)
- }
- return self._callServer('reportTCResult', argsAPI)
+ # getTestCaseKeywords returns a dictionary like
+ # {'12622': {'34': 'KeyWord01', '36': 'KeyWord03'}}
+ # key is the testcaseid, why that? cause it is possible to ask for
+ # a set of test cases. we are just interested in one tc
+ a_keyword_dic = self.getTestCaseKeywords(testcaseid=a_tc_id )[a_tc_id]
+ keywords = a_keyword_dic.values()
+ return list(keywords)
-
- def uploadExecutionAttachment(self,attachmentfile,executionid,title,description):
- """
- Attach a file to a test execution
- attachmentfile: python file descriptor pointing to the file
- name : name of the file
- title : title of the attachment
- description : description of the attachment
- content type : mimetype of the file
+ def listKeywordsForTS(self, internal_ts_id):
+ """ Returns dictionary with keyword lists for all test cases of
+ test suite with id == INTERNAL_TS_ID
"""
- import mimetypes
- import base64
- import os.path
- argsAPI={'devKey' : self.devKey,
- 'executionid':executionid,
- 'title':title,
- 'filename':os.path.basename(attachmentfile.name),
- 'description':description,
- 'filetype':mimetypes.guess_type(attachmentfile.name)[0],
- 'content':base64.encodestring(attachmentfile.read())
- }
- return self._callServer('uploadExecutionAttachment', argsAPI)
-
+
+ a_ts_id = str(internal_ts_id)
+ all_tc_for_ts = self.getTestCasesForTestSuite(a_ts_id, False,
+ 'full', getkeywords=True)
+ response = {}
+ for a_ts_tc in all_tc_for_ts:
+ tc_id = a_ts_tc['id']
+ keyword_details = a_ts_tc.get('keywords', {})
+ if sys.version_info[0] < 3:
+ keywords = map((lambda x: x['keyword']), keyword_details.values())
+ else:
+ keywords = [kw['keyword'] for kw in keyword_details.values()]
+ response[tc_id] = keywords
+
+ return response
+
#
# ADDITIONNAL FUNCTIONS
#
@@ -496,7 +448,7 @@ def countTestCasesTS(self):
def countPlatforms(self):
""" countPlatforms :
- Count all the Platforms
+ Count all the Platforms in TestPlans
"""
projects=self.getProjects()
nbPlatforms = 0
@@ -526,21 +478,15 @@ def listProjects(self):
"""
projects=self.getProjects()
for project in projects:
- print "Name: %s ID: %s " % (project['name'], project['id'])
+ print("Name: %s ID: %s " % (project['name'], project['id']))
def initStep(self, actions, expected_results, execution_type):
""" initStep :
Initializes the list which stores the Steps of a Test Case to create
"""
- self.stepsList = []
- lst = {}
- lst['step_number'] = '1'
- lst['actions'] = actions
- lst['expected_results'] = expected_results
- lst['execution_type'] = str(execution_type)
- self.stepsList.append(lst)
- return True
+ self._emptyStepsList()
+ return self.appendStep(actions, expected_results, execution_type)
def appendStep(self, actions, expected_results, execution_type):
""" appendStep :
@@ -552,31 +498,77 @@ def appendStep(self, actions, expected_results, execution_type):
lst['expected_results'] = expected_results
lst['execution_type'] = str(execution_type)
self.stepsList.append(lst)
- return True
+ return True
+
+ def _emptyStepsList(self):
+ """ reset .stepsList to an empty List """
+ self.stepsList = []
- def getProjectIDByName(self, projectName):
- projects=self.getProjects()
- result=-1
- for project in projects:
- if (project['name'] == projectName):
- result = project['id']
- break
- return result
-
- def __str__(self):
- message = """
-TestlinkAPIClient - class %s - version %s
-@author: Olivier Renault, James Stock, TestLink-API-Python-client developers
-"""
- return message % (self.__class__.__name__, self.__VERSION__)
+ def getProjectIDByName(self, projectName):
+ try:
+ project=self.getTestProjectByName(projectName)
+ userID = project['id']
+ except KeyError:
+ userID = -1
+
+ return userID
+
+ def ensureUserExist(self, login, **userArgs):
+ """ combines getUserByLogin() + createUser()
+ creates new user only, when login not exist
+
+ returns userID
+
+ userArgs defines optional key value pairs used to create new user
+ - firstname Default 'unknown'
+ - lastname Default 'via pyTLapi'
+ - email Default 'unknown@example.com'
+ - password Default None
+ """
+
+ try:
+ response = self.getUserByLogin(login)
+ userID = response[0]['dbID']
+ except TLResponseError as tl_err:
+ if tl_err.code == 10000:
+ # Cannot Find User Login create new user
+ name1 = userArgs.get('firstname', 'unknown')
+ name2 = userArgs.get('lastname', 'via pyTLapi')
+ mail = userArgs.get('email', 'unknown@example.com')
+ pw = userArgs.get('password')
+ userID = self.createUser(login, name1, name2, mail, password=pw)
+ else:
+ # seems to be another response failure - we forward it
+ raise
+
+ return userID
+ def ensureUserExistWithProjectRole(self, login, rolename, projectname, **userArgs):
+ """ combines ensureUserExist() + setUserRoleOnProject()
+ creates new user only, when login not exist
+
+ returns list with users account details
+
+ rolename
+ - e.g. tester, test designer, senior tester, guest ...
+
+ userArgs defines optional key value pairs used to create new user
+ - firstname Default 'unknown'
+ - lastname Default 'via pyTLapi'
+ - email Default 'unknown@example.com'
+ - password Default None
+ """
+
+ userID = self.ensureUserExist(login, **userArgs)
+ projectID = self.getProjectIDByName(projectname)
+ self.setUserRoleOnProject(userID, rolename, projectID)
+ return self.getUserByID(userID)
if __name__ == "__main__":
tl_helper = TestLinkHelper()
tl_helper.setParamsFromArgs()
myTestLink = tl_helper.connect(TestlinkAPIClient)
- print myTestLink
- print myTestLink.about()
+ print(myTestLink)
diff --git a/src/testlink/testlinkapigeneric.py b/src/testlink/testlinkapigeneric.py
new file mode 100644
index 0000000..a534fd4
--- /dev/null
+++ b/src/testlink/testlinkapigeneric.py
@@ -0,0 +1,2375 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+import sys, os.path
+IS_PY3 = sys.version_info[0] > 2
+if not IS_PY3:
+ # importing required py2 modules
+ import xmlrpclib as xmlrpc_client
+ # in py 3 encodestring is deprecated and an alias for encodebytes
+ # see issue #39 and compare py2 py3 doc
+ # https://docs.python.org/2/library/base64.html#base64.encodestring
+ # https://docs.python.org/3/library/base64.html#base64.encodebytes
+ from base64 import encodestring as encodebytes
+else:
+ # importing required py3 modules
+ import xmlrpc.client as xmlrpc_client
+ from base64 import encodebytes
+
+from platform import python_version
+from mimetypes import guess_type
+
+from . import testlinkerrors
+from .testlinkhelper import TestLinkHelper, VERSION
+from .testlinkargs import getMethodsWithPositionalArgs, getArgsForMethod
+from .testlinkdecorators import decoApiCallAddAttachment,\
+decoApiCallAddDevKey, decoApiCallWithoutArgs, \
+decoMakerApiCallReplaceTLResponseError, decoMakerApiCallWithArgs, \
+decoMakerApiCallChangePosToOptArg
+
+class TestlinkAPIGeneric(object):
+ """ client for XML-RPC communication between Python and TestLink
+ Implements the TestLink API methods as generic PY methods with
+ error handling.
+
+ Allows the configuration of arguments for these API method as positional
+ or optional arguments.
+
+ Changes of TestLink API methods should be implemented in this base class.
+ Service Methods like "countProjects" should be implemented on subclasses
+ like TestlinkAPIClient
+ """
+
+ __slots__ = ['server', 'devKey', '_server_url', '_positionalArgNames']
+
+ __version__ = VERSION
+ __author__ = 'Luiko Czub, TestLink-API-Python-client developers'
+
+ def __init__(self, server_url, devKey, **args):
+ transport=args.get('transport')
+ encoding=args.get('encoding')
+ verbose=args.get('verbose',False)
+ allow_none=args.get('allow_none',False)
+ use_datetime = args.get('use_datetime', False)
+ context = args.get('context', None)
+ self.server = xmlrpc_client.ServerProxy(server_url, transport, encoding,
+ verbose, allow_none, use_datetime,
+ context=context)
+ self.devKey = devKey
+ self._server_url = server_url
+ self._positionalArgNames = getMethodsWithPositionalArgs()
+
+
+
+ # GENERIC API CALLS - using decorators
+ # http://stackoverflow.com/questions/1015307/python-bind-an-unbound-method
+
+ # Method definitions should be build either with
+ # @decoMakerApiCallWithArgs(argNamesPositional, argNamesOptional)
+ # for calling a server method with arguments
+ # - argNamesPositional = list default positional args
+ # - argNamesOptional = list additional optional args
+ # to check the server response, if it includes TestLink Error Codes or
+ # an empty result (which raise a TLResponseError)
+ # or
+ # @decoApiCallWithoutArgs
+ # for calling server methods without arguments
+ # to check the server response, if it includes TestLink Error Codes or
+ # an empty result (which raise a TLResponseError)
+ #
+ # Additional behavior could be added with
+ #
+ # @decoApiCallAddDevKey
+ # - to expand the parameter list with devKey key/value pair
+ # before calling the server method
+ # @decoMakerApiCallReplaceTLResponseError(replaceCode)
+ # - to catch an TLResponseError after calling the server method and
+ # with an empty list
+ # - replaceCode : TestLink Error Code, which should be handled
+ # default is None for "Empty Results"
+ # @decoApiCallAddAttachment(methodAPI):
+ # - to add an mandatory argument 'attachmentfile'
+ # - attachmentfile is a python file descriptor pointing to the file
+ # - to expand parameter list with key/value pairs
+ # 'filename', 'filetype', 'content'
+ # from 'attachmentfile' before calling the server method
+
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid'])
+ def getLatestBuildForTestPlan(self):
+ """ Gets the latest build by choosing the maximum build id for a specific test plan """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid'],
+ ['testcaseid', 'testcaseexternalid',
+ 'platformid', 'platformname',
+ 'buildid', 'buildname', 'options'])
+ def getLastExecutionResult(self):
+ """ Gets the result of LAST EXECUTION for a particular testcase on a test plan.
+If there are no filter criteria regarding platform and build,
+result will be get WITHOUT checking for a particular platform and build.
+
+following optional arguments could only used with
+TL version >= 1.9.9
+- platformid, platformname, buildid, buildname
+
+TL version >= 1.9.11
+- options : dictionary with key value pair
+ 'getBugs' : True / False
+"""
+
+ @decoApiCallWithoutArgs
+ def sayHello(self):
+ """ Lets you see if the server is up and running """
+
+ def ping(self):
+ """ alias for methodAPI sayHello """
+ return self.sayHello()
+
+ @decoMakerApiCallWithArgs(['str'])
+ def repeat(self):
+ """ Repeats a message back """
+
+ @decoApiCallWithoutArgs
+ def about(self):
+ """ Gives basic information about the API """
+
+# /**
+# * Creates a new build for a specific test plan
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["testplanid"]
+# * @param string $args["buildname"];
+# * @param string $args["buildnotes"];
+# * @param string $args["active"];
+# * @param string $args["open"];
+# * @param string $args["releasedate"]: YYYY-MM-DD;
+# * @param int $args["copytestersfrombuild"] OPTIONAL,
+# * if > 0 and valid buildid tester assignments will be copied.
+# *
+# * @return mixed $resultInfo
+# *
+# * @access public
+# */
+# public function createBuild($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid', 'buildname'],
+ ['buildnotes', 'active', 'open', 'releasedate',
+ 'copytestersfrombuild'])
+ def createBuild(self):
+ """ Creates a new build for a specific test plan
+
+ active : 1 (default) = activ 0 = inactiv
+ open : 1 (default) = open 0 = closed
+ releasedate : YYYY-MM-DD
+ copytestersfrombuild : valid buildid tester assignments will be copied.
+ """
+
+ @decoMakerApiCallReplaceTLResponseError()
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['buildid'])
+ def closeBuild(self):
+ """ Close build
+
+ buildid - ATTENTION must be an integer
+ - createBuild returns the id as a string
+ - convert it with int() before calling closeBuild()
+ """
+
+ @decoMakerApiCallReplaceTLResponseError()
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs()
+ def getProjects(self):
+ """ Gets a list of all projects
+
+ returns an empty list, if no test project exist """
+
+ @decoMakerApiCallReplaceTLResponseError()
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectid'])
+ def getProjectTestPlans(self):
+ """ Gets a list of test plans within a project
+
+ returns an empty list, if no testplan is assigned """
+
+ @decoMakerApiCallReplaceTLResponseError()
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid'])
+ def getBuildsForTestPlan(self):
+ """ Gets a list of builds within a test plan
+
+ returns an empty list, if no build is assigned """
+
+ @decoMakerApiCallReplaceTLResponseError()
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid'])
+ def getTestSuitesForTestPlan(self):
+ """ List test suites within a test plan alphabetically
+
+ returns an empty list, if no suite is assigned """
+
+
+# /**
+# * create a test project
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param string $args["testprojectname"]
+# * @param string $args["testcaseprefix"]
+# * @param string $args["notes"] OPTIONAL
+# * @param map $args["options"] OPTIONAL ALL int treated as boolean
+# * keys requirementsEnabled,testPriorityEnabled,automationEnabled,inventoryEnabled
+# *
+# * @param int $args["active"] OPTIONAL
+# * @param int $args["public"] OPTIONAL
+# * @param string $args["itsname"] OPTIONAL
+# * @param boolean $args["itsEnabled"] OPTIONAL
+# *
+# *
+# * @return mixed $resultInfo
+# */
+# public function createTestProject($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectname', 'testcaseprefix'],
+ ['notes', 'active', 'public', 'options',
+ 'itsname', 'itsenabled'])
+ def createTestProject(self):
+ """ Create a test project
+
+ options : dictionary with keys
+ requirementsEnabled, testPriorityEnabled,
+ automationEnabled,inventoryEnabled
+ and values 0 (false) and 1 (true) """
+
+ @decoMakerApiCallReplaceTLResponseError()
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testsuiteid'], ['deep', 'details',
+ 'getkeywords'])
+ def getTestCasesForTestSuite(self):
+ """ List test cases within a test suite alphabetically
+
+ details - default is 'simple',
+ use 'full' if you want to get summary,steps & expected_results
+ or 'only_id', if you just need an ID list
+
+ deep - True/False - default is True
+ if True, return also test case of child suites
+
+ getkeywords - True/False - default is False
+ if True AND details='full', dictionary includes for each test
+ case, which as assigned keywords, an additional key value pair
+ 'keywords'
+
+ returns an empty list, if no build is assigned """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testcasename'],
+ ['testsuitename', 'testprojectname', 'testcasepathname'])
+ def getTestCaseIDByName(self):
+ """ Find a test case by its name
+
+ testcasepathname : Full test case path name,
+ starts with test project name , pieces separator -> ::
+ server return can be a list or a dictionary
+ - optional arg testprojectname seems to create a dictionary response """
+
+# /**
+# * createTestCase
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param string $args["testcasename"]
+# * @param int $args["testsuiteid"]: test case parent test suite id
+# * @param int $args["testprojectid"]: test case parent test suite id
+# *
+# * @param string $args["authorlogin"]: to set test case author
+# * @param string $args["summary"]
+# * @param array $args["steps"]
+# *
+# * @param string $args["preconditions"] - optional
+# * @param int $args["importance"] - optional - see const.inc.php for domain
+# * @param int $args["execution"] - optional - see ... for domain
+# * @param int $args["order'] - optional
+# * @param int $args["internalid"] - optional - do not use
+# * @param string $args["checkduplicatedname"] - optional
+# * @param string $args["actiononduplicatedname"] - optional
+# * @param int $args["status"] - optional - see const.inc.php $tlCfg->testCaseStatus
+# * @param number $args["estimatedexecduration"] - optional
+# *
+# * @return mixed $resultInfo
+# * @return string $resultInfo['operation'] - verbose operation
+# * @return boolean $resultInfo['status'] - verbose operation
+# * @return int $resultInfo['id'] - test case internal ID (Database ID)
+# * @return mixed $resultInfo['additionalInfo']
+# * @return int $resultInfo['additionalInfo']['id'] same as $resultInfo['id']
+# * @return int $resultInfo['additionalInfo']['external_id'] without prefix
+# * @return int $resultInfo['additionalInfo']['status_ok'] 1/0
+# * @return string $resultInfo['additionalInfo']['msg'] - for debug
+# * @return string $resultInfo['additionalInfo']['new_name'] only present if new name generation was needed
+# * @return int $resultInfo['additionalInfo']['version_number']
+# * @return boolean $resultInfo['additionalInfo']['has_duplicate'] - for debug
+# * @return string $resultInfo['message'] operation message
+# */
+# public function createTestCase($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testcasename', 'testsuiteid', 'testprojectid',
+ 'authorlogin', 'summary', 'steps'],
+ ['preconditions', 'importance', 'executiontype', 'order',
+ 'internalid', 'checkduplicatedname', 'actiononduplicatedname',
+ 'status', 'estimatedexecduration'])
+ def createTestCase(self):
+ """ Create a test case
+
+ steps is a list with dictionaries , example
+ [{'step_number' : 1, 'actions' : "action A" ,
+ 'expected_results' : "result A", 'execution_type' : 0},
+ {'step_number' : 2, 'actions' : "action B" ,
+ 'expected_results' : "result B", 'execution_type' : 1},
+ {'step_number' : 3, 'actions' : "action C" ,
+ 'expected_results' : "result C", 'execution_type' : 0}]
+
+ possible values for optional arguments testlink/cfg/const.inc.php
+ importance: 1 (low) 2 (medium) 3 (high)
+ status: 1 (draft) 2 (readyForReview)
+ 3 (reviewInProgress) 4 (rework)
+ 5 (obsolete) 6 (future)
+ 7 (final)
+ executiontype: 1 (Manual) 2 (Automated)
+ """
+
+# /**
+# * Reports a result for a single test case
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["testcaseid"]: optional, if not present
+# * testcaseexternalid must be present
+# *
+# * @param int $args["testcaseexternalid"]: optional, if does not is present
+# * testcaseid must be present
+# *
+# *
+# *
+# * @param int $args["testplanid"]
+# * @param string $args["status"] - status is {@link $validStatusList}
+# * @param int $args["buildid"] - optional.
+# * if not present and $args["buildname"] exists
+# * then
+# * $args["buildname"] will be checked and used if valid
+# * else
+# * build with HIGHEST ID will be used
+# *
+# * @param int $args["buildname"] - optional.
+# * if not present Build with higher internal ID will be used
+# *
+# *
+# * @param string $args["notes"] - optional
+# * @param string $args["execduration"] - optional
+# *
+# * @param bool $args["guess"] - optional defining whether to guess optinal params or require them
+# * explicitly default is true (guess by default)
+# *
+# * @param string $args["bugid"] - optional
+# *
+# * @param string $args["platformid"] - optional, if not present platformname must be present
+# * @param string $args["platformname"] - optional, if not present platformid must be present
+# *
+# *
+# * @param string $args["customfields"] - optional
+# * contains an map with key:Custom Field Name, value: value for CF.
+# * VERY IMPORTANT: value must be formatted in the way it's written to db,
+# * this is important for types like:
+# *
+# * DATE: strtotime()
+# * DATETIME: mktime()
+# * MULTISELECTION LIST / CHECKBOX / RADIO: se multipli selezione ! come separatore
+# *
+# *
+# * these custom fields must be configured to be writte during execution.
+# * If custom field do not meet condition value will not be written
+# *
+# * @param boolean $args["overwrite"] - optional, if present and true, then last execution
+# * for (testcase,testplan,build,platform) will be overwritten.
+# *
+# * @param boolean $args["user"] - optional, if present and user is a valid login
+# * (no other check will be done) it will be used when writting execution.
+# *
+# * @param string $args["timestamp"] - optional, if not present now is used
+# * format YYYY-MM-DD HH:MM:SS
+# * example 2015-05-22 12:15:45
+# * @return mixed $resultInfo
+# * [status] => true/false of success
+# * [id] => result id or error code
+# * [message] => optional message for error message string
+# * @access public
+# *
+# * @internal revisions
+# *
+# */
+# public function reportTCResult($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid', 'status'],
+ ['testcaseid', 'testcaseexternalid', 'buildid', 'buildname',
+ 'platformid', 'platformname', 'notes', 'guess', 'bugid',
+ 'customfields', 'overwrite', 'user', 'execduration',
+ 'timestamp', 'steps'])
+ def reportTCResult(self):
+ """ Reports a result for a single test case
+
+ args variations: testcaseid - testcaseexternalid
+ buildid - buildname
+ platformid - platformname
+
+ customfields : dictionary with customfields names + values
+ VERY IMPORTANT: value must be formatted in the way it's written to db
+ overwrite : if present and true, then last execution for
+ (testcase,testplan,build,platform) will be overwritten.
+ user : if present and user is a valid login (no other check will be done)
+ it will be used when writing execution.
+ execduration : Exec (min) as float (2.5 = 2min 30sec)
+ timestamp : 'YYYY-MM-DD hh:mm[:ss]'#
+ steps : [{'step_number' : 6, 'result' : 'p', 'notes" : 'a_note'},
+ {'step_number' : 7, 'result' : 'f', 'notes" : 'blabla'}]
+ """
+
+# /**
+# * turn on/off testMode
+# *
+# * This method is meant primarily for testing and debugging during development
+# * @param struct $args
+# * @return boolean
+# * @access protected
+# */ testmode
+# public function setTestMode($args)
+
+
+ #FIXME: LC 20140202 makes setTestMode sense in python client?
+ # xmlrpc calls are isolated transactions.
+ # the second call works with another TL api instance than the frist one
+ # so the second call works with default TestMode settings (false), even if
+ # the first call has active the TestMode.
+ # So using setTestMode make only sense, if the php api instance is directly
+ # used
+# @decoMakerApiCallWithArgs(['testmode'])
+# def setTestMode(self):
+# """ turn on/off testMode
+# This method is meant primarily for testing and debugging during development
+# """
+
+# /**
+# * getTestCasesForTestPlan
+# * List test cases linked to a test plan
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["testplanid"]
+# * @param int $args["buildid"] - optional
+# * @param int $args["platformid"] - optional
+# * @param int $args["testcaseid"] - optional
+# * @param int $args["keywordid"] - optional mutual exclusive with $args["keywords"]
+# * @param int $args["keywords"] - optional mutual exclusive with $args["keywordid"]
+# *
+# * @param boolean $args["executed"] - optional
+# * @param int $args["$assignedto"] - optional
+# * @param string $args["executestatus"] - optional
+# * @param array $args["executiontype"] - optional
+# * @param array $args["getstepinfo"] - optional - default false
+# * @param string $args["details"] - optional
+# * 'full': (default) get summary,steps,expected_results,test suite name
+# * 'simple':
+# * 'details':
+# * @param array $args["customfields"]
+# * - optional can be a boolean or an array with the requested fields
+# * @return mixed $resultInfo
+# *
+# * @internal revisions
+# * @since 1.9.13
+# * 20141230 - franciscom - TICKET 6805: platform parameter
+# */
+# public function getTestCasesForTestPlan($args)
+
+ @decoMakerApiCallReplaceTLResponseError()
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid'],
+ ['buildid', 'platformid',
+ 'testcaseid', 'keywordid', 'keywords', 'executed', 'assignedto',
+ 'executestatus', 'executiontype', 'getstepinfo', 'details',
+ 'customfields'])
+ def getTestCasesForTestPlan(self):
+ """ List test cases linked to a test plan
+
+ details - default is 'full',
+ 'simple', 'details' ??
+
+ args variations: keywordid - keywords
+
+ returns an empty list, if no build is assigned """
+
+ @decoMakerApiCallReplaceTLResponseError(replaceValue='')
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testcaseexternalid', 'version', 'testprojectid',
+ 'customfieldname'], ['details'])
+ def getTestCaseCustomFieldDesignValue(self):
+ """ Gets value of a Custom Field with scope='design' for a given Test case
+
+ details = changes output information
+ null or 'value' => just value
+ 'full' => a map with all custom field definition
+ plus value and internal test case id
+ 'simple' => value plus custom field name, label, and type (as code).
+
+ attention - be careful with testcaseexternalid - it must include an '-'.
+ otherwise TL (<=1.9.8) returns
+ """
+
+# /**
+# * Add a test case version to a test plan
+# *
+# * @param args['testprojectid']
+# * @param args['testplanid']
+# * @param args['testcaseexternalid']
+# * @param args['version']
+# * @param args['platformid'] - OPTIONAL Only if test plan has no platforms
+# * @param args['executionorder'] - OPTIONAL
+# * @param args['urgency'] - OPTIONAL
+# * @param args['overwrite'] - OPTIONAL
+# *
+# */
+# public function addTestCaseToTestPlan($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectid',
+ 'testplanid', 'testcaseexternalid', 'version'],
+ ['platformid', 'executionorder', 'urgency', 'overwrite'])
+ def addTestCaseToTestPlan(self):
+ """ Add a test case version to a test plan """
+
+ @decoMakerApiCallReplaceTLResponseError(7008)
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectid'])
+ def getFirstLevelTestSuitesForTestProject(self):
+ """ get set of test suites AT TOP LEVEL of tree on a Test Project
+
+ returns an empty list, if no suite is assigned (api error 7008)
+ - details see comments for decoMakerApiCallReplaceTLResponseError """
+
+# /**
+# * Assign Requirements to a test case
+# * we can assign multiple requirements.
+# * Requirements can belong to different Requirement Spec
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["testcaseexternalid"]
+# * @param int $args["testprojectid"]
+# * @param string $args["requirements"]
+# * array(array('req_spec' => 1,'requirements' => array(2,4)),
+# * array('req_spec' => 3,'requirements' => array(22,42))
+# *
+# */
+# public function assignRequirements($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testcaseexternalid', 'testprojectid',
+ 'requirements'])
+ def assignRequirements(self):
+ """ Assign Requirements to a test case
+ It is possible to assign multiple requirements, belonging to different
+ requirement specifications. (the internal IDs must be known!)
+
+ Argument REQUIREMENTS expects an array of dictionaries, example:
+ .assignRequirements('GPROAPI4-2', 6652,
+ [{'req_spec' : 6729, 'requirements' : [6731]},
+ {'req_spec' : 6733, 'requirements' : [6735, 6737]}])
+ This would assign to testcase 'GPROAPI4-2' (in testproject with id 6652)
+ a) requirement with ID 6731 of requirement spec 6729 AND
+ b) requirements with ID 6735 and 6737 of requirement spec 6733
+ """
+
+# /**
+# * Gets attachments for specified test case VERSION.
+# * The attachment file content is Base64 encoded. To save the file to disk in client,
+# * Base64 decode the content and write file in binary mode.
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * Developer key
+# * @param int $args["testcaseid"]:
+# * optional, if does not is present
+# * testcaseexternalid must be present
+# *
+# * @param int $args["version"]:
+# * optional, if not present, the latest version will be used
+# *
+# * @param int $args["testcaseexternalid"]:
+# * optional, if does not is present
+# * testcaseid must be present
+# *
+# * @return mixed $resultInfo
+# */
+# public function getTestCaseAttachments($args) {
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs([], ['testcaseid', 'version',
+ 'testcaseexternalid'])
+ def getTestCaseAttachments(self):
+ """ Gets attachments for specified test case.
+
+ args variations: testcaseid - testcaseexternalid
+
+ version - optional, if not present, the latest test case version
+ will be used
+
+ The attachment file content is Base64 encoded. To save the file to disk
+ in client, Base64 decode the content and write file in binary mode. """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectid', 'testsuitename', 'details'],
+ ['parentid', 'order', 'checkduplicatedname',
+ 'actiononduplicatedname'])
+ def createTestSuite(self):
+ """ create a test suite """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectname'])
+ def getTestProjectByName(self):
+ """ Gets info about target test project """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectname', 'testplanname'])
+ def getTestPlanByName(self):
+ """ Gets info about target test project """
+
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs([], ['testcaseid', 'testcaseexternalid', 'version'])
+ def getTestCase(self):
+ """ get test case specification using external or internal id
+
+ attention - be careful with testcaseexternalid - it must include an '-'.
+ otherwise TL (<=1.9.8) returns
+ """
+
+ @decoMakerApiCallChangePosToOptArg(2,'testprojectname')
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanname'],
+ ['testprojectname', 'prefix', 'note', 'active', 'public'])
+ def createTestPlan(self):
+ """ create a test plan
+
+ args variations: testprojectname - prefix
+
+ supports also pre 1.9.14 arg definition, where 'testprojectname'
+ was mandatory ('prefix' comes as alternative with 1.9.14)
+
+ examples:
+ - createTestPlan('aTPlanName', 'aTProjectName')
+ - createTestPlan('aTPlanName', testprojectname='aTProjectName')
+ - createTestPlan('aTPlanName', prefix='aTProjectPrefix')
+
+ """
+
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['nodeid'])
+ def getFullPath(self):
+ """ Gets full path from the given node till the top using nodes_hierarchy_table
+
+ nodeid = can be just a single id or a list with ids
+ ATTENTION: id must be an integer. """
+
+# /**
+# * delete an execution
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["executionid"]
+# *
+# * @return mixed $resultInfo
+# * [status] => true/false of success
+# * [id] => result id or error code
+# * [message] => optional message for error message string
+# * @access public
+# */
+# public function deleteExecution($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['executionid'])
+ def deleteExecution(self):
+ """ delete an execution
+
+ executionid - ATTENTION must be an integer
+ - reportTCResult returns the id as a string
+ - convert it with int() before calling deleteExecution()
+
+ Default TL server configuration does not allow deletion of exections
+ see Installation & Configuration Manual Version 1.9
+ chap. 5.8. Test execution settings
+ $tlCfg->exec_cfg->can_delete_execution """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testsuiteid'])
+ def getTestSuiteByID(self):
+ """ Return a TestSuite by ID """
+
+ @decoMakerApiCallReplaceTLResponseError()
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testsuiteid'])
+ def getTestSuitesForTestSuite(self):
+ """ get list of TestSuites which are DIRECT children of a given TestSuite
+
+ returns an empty list, if no TestSuite is assigned """
+
+ @decoMakerApiCallReplaceTLResponseError(3041)
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid'])
+ def getTestPlanPlatforms(self):
+ """ Returns the list of platforms associated to a given test plan
+
+ returns an empty list, if no platform is assigned (api error 3041)
+ - details see comments for decoMakerApiCallReplaceTLResponseError """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid'])
+ def getTotalsForTestPlan(self):
+ """ Gets the summarized results grouped by platform. """
+
+ @decoMakerApiCallWithArgs(['user'])
+ def doesUserExist(self):
+ """ Checks if user name exists
+ returns true if everything OK, otherwise error structure """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['devKey'])
+ def checkDevKey(self):
+ """ check if Developer Key exists
+ returns true if everything OK, otherwise error structure """
+
+# /**
+# * Uploads an attachment for a Requirement Specification.
+# *
+# * The attachment content must be Base64 encoded by the client before sending it.
+# *
+# * @param struct $args
+# * @param string $args["devKey"] Developer key
+# * @param int $args["reqspecid"] The Requirement Specification ID
+# * @param string $args["title"] (Optional) The title of the Attachment
+# * @param string $args["description"] (Optional) The description of the Attachment
+# * @param string $args["filename"] The file name of the Attachment (e.g.:notes.txt)
+# * @param string $args["filetype"] The file type of the Attachment (e.g.: text/plain)
+# * @param string $args["content"] The content (Base64 encoded) of the Attachment
+# *
+# * @since 1.9beta6
+# * @return mixed $resultInfo an array containing the fk_id, fk_table, title,
+# * description, file_name, file_size and file_type. If any errors occur it
+# * returns the error map.
+# */
+# public function uploadRequirementSpecificationAttachment($args)
+
+ @decoApiCallAddAttachment
+ @decoMakerApiCallWithArgs(['reqspecid'],
+ ['title', 'description', 'filename', 'filetype', 'content'])
+ def uploadRequirementSpecificationAttachment(self):
+ """ Uploads an attachment for a Requirement Specification.
+
+ reqspecid - The Requirement Specification ID
+
+ mandatory non api args: attachmentfile
+ - python file descriptor pointing to the file
+ - or a valid file path
+
+ default values for filename, filetype, content are determine from
+ ATTACHMENTFILE, but user could overwrite it, if user want to store the
+ attachment with a different name
+ """
+
+ @decoApiCallAddAttachment
+ @decoMakerApiCallWithArgs(['requirementid'],
+ ['title', 'description', 'filename', 'filetype', 'content'])
+ def uploadRequirementAttachment(self):
+ """ Uploads an attachment for a Requirement.
+
+ requirementid - The Requirement ID
+
+ mandatory non api args: attachmentfile
+ - python file descriptor pointing to the file
+ - or a valid file path
+
+ default values for filename, filetype, content are determine from
+ ATTACHMENTFILE, but user could overwrite it, if user want to store the
+ attachment with a different name
+ """
+
+ @decoApiCallAddAttachment
+ @decoMakerApiCallWithArgs(['testprojectid'],
+ ['title', 'description', 'filename', 'filetype', 'content'])
+ def uploadTestProjectAttachment(self):
+ """ Uploads an attachment for a Test Project.
+
+ testprojectid - The Test Project ID
+
+ mandatory non api args: attachmentfile
+ - python file descriptor pointing to the file
+ - or a valid file path
+
+ default values for filename, filetype, content are determine from
+ ATTACHMENTFILE, but user could overwrite it, if user want to store the
+ attachment with a different name
+ """
+
+ @decoApiCallAddAttachment
+ @decoMakerApiCallWithArgs(['testsuiteid'],
+ ['title', 'description', 'filename', 'filetype', 'content'])
+ def uploadTestSuiteAttachment(self):
+ """ Uploads an attachment for a Test Suite.
+
+ testsuiteid - The Test Suite ID
+
+ mandatory non api args: attachmentfile
+ - python file descriptor pointing to the file
+ - or a valid file path
+
+ default values for filename, filetype, content are determine from
+ ATTACHMENTFILE, but user could overwrite it, if user want to store the
+ attachment with a different name
+ """
+# /**
+# * Uploads an attachment for a Test Case.
+# *
+# * The attachment content must be Base64 encoded by the client before sending it.
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * Developer key
+# * @param int $args["testcaseid"]
+# * Test Case INTERNAL ID
+# * @param int $args["version"]
+# * version number
+# *
+# * @param string $args["title"]
+# * (Optional) The title of the Attachment
+# * @param string $args["description"]
+# * (Optional) The description of the Attachment
+# * @param string $args["filename"]
+# * The file name of the Attachment(e.g.:notes.txt)
+# * @param string $args["filetype"]
+# * The file type of the Attachment(e.g.: text/plain)
+# * @param string $args["content"]
+# * The content(Base64 encoded) of the Attachment
+# *
+# * @return mixed $resultInfo an array containing the fk_id, fk_table, title,
+# * description, file_name, file_size and file_type. If any errors occur it
+# * returns the erros map.
+# */
+# public function uploadTestCaseAttachment($args) {
+
+ @decoApiCallAddAttachment
+ @decoMakerApiCallWithArgs(['testcaseid', 'version'],
+ ['title', 'description', 'filename', 'filetype', 'content'])
+ def uploadTestCaseAttachment(self):
+ """ Uploads an attachment for a Test Case.
+
+ testcaseid - Test Case INTERNAL ID
+ version - Test Case version number
+
+ mandatory non api args: attachmentfile
+ - python file descriptor pointing to the file
+ - or a valid file path
+
+ default values for filename, filetype, content are determine from
+ ATTACHMENTFILE, but user could overwrite it, if user want to store the
+ attachment with a different name
+ """
+
+ @decoApiCallAddAttachment
+ @decoMakerApiCallWithArgs(['executionid'],
+ ['title', 'description', 'filename', 'filetype', 'content'])
+ def uploadExecutionAttachment(self):
+ """ Uploads an attachment for an execution
+
+ executionid - execution ID
+
+ mandatory non api args: attachmentfile
+ - python file descriptor pointing to the file
+ - or a valid file path
+
+ default values for filename, filetype, content are determine from
+ ATTACHMENTFILE, but user could overwrite it, if user want to store the
+ attachment with a different name
+ """
+
+
+ @decoApiCallAddAttachment
+ @decoMakerApiCallWithArgs(['fkid', 'fktable'],
+ ['title', 'description', 'filename', 'filetype', 'content'])
+ def uploadAttachment(self):
+ """ Uploads an attachment for an execution
+
+ fkid - The Attachment Foreign Key ID
+ fktable - The Attachment Foreign Key Table
+
+ mandatory non api args: attachmentfile
+ - python file descriptor pointing to the file
+ - or a valid file path
+
+ default values for filename, filetype, content are determine from
+ ATTACHMENTFILE, but user could overwrite it, if user want to store the
+ attachment with a different name
+ """
+
+# /**
+# * Gets value of a Custom Field for a entity in a given scope (e.g.: a custom
+# * field for a test case in design scope).
+# *
+# * BUGID-4188: feature request - new method - getTestSuiteCustomFieldValue
+# *
+# * @param struct $args
+# * @param string $args["devKey"]: used to check if operation can be done.
+# * if devKey is not valid => abort.
+# *
+# * @param string $args["customfieldname"]: custom field name
+# * @param int $args["testprojectid"]: project id
+# * @param string $args["nodetype"]: note type (testcase, testsuite, ...)
+# * @param int $args["nodeid"]: node id (test case version id, project id, ...)
+# * @param string $args["scope"]: cf scope (execution, design or testplan_design)
+# * @param int $args["executionid"]: execution id
+# * @param int $args["testplanid"]: test plan id
+# * @param int $args["linkid"]: link id for nodes linked at test plan design scope
+# *
+# * @return mixed $resultInfo
+# *
+# * @access protected
+# */
+# protected function getCustomFieldValue($args)
+
+# /**
+# * Gets a Custom Field of a Test Case in Execution Scope.
+# *
+# * @param struct $args
+# * @param string $args["devKey"]: used to check if operation can be done.
+# * if devKey is not valid => abort.
+# *
+# * @param string $args["customfieldname"]: custom field name
+# * @param int $args["testprojectid"]: project id
+# * @param int $args["version"]: test case version id
+# * @param int $args["executionid"]: execution id
+# * @param int $args["testplanid"]: test plan id
+# *
+# * @return mixed $resultInfo
+# *
+# * @access public
+# */
+# public function getTestCaseCustomFieldExecutionValue($args)
+
+ @decoMakerApiCallReplaceTLResponseError(replaceValue=None)
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['customfieldname', 'testprojectid', 'version',
+ 'executionid', 'testplanid'])
+ def getTestCaseCustomFieldExecutionValue(self):
+ """ Gets a Custom Field of a Test Case in Execution Scope. """
+
+
+# /**
+# * Gets a Custom Field of a Test Case in Test Plan Design Scope.
+# *
+# * @param struct $args
+# * @param string $args["devKey"]: used to check if operation can be done.
+# * if devKey is not valid => abort.
+# *
+# * @param string $args["customfieldname"]: custom field name
+# * @param int $args["testcaseid"]: project id
+# * @param int $args["version"]: test case version id
+# * @param int $args["testplanid"]: test plan id
+# * @param int $args["linkid"]: link id (important!)
+# *
+# * @return mixed $resultInfo
+# *
+# * @access public
+# */
+# public function getTestCaseCustomFieldTestPlanDesignValue($args)
+
+ @decoMakerApiCallReplaceTLResponseError(replaceValue=None)
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['customfieldname', 'testprojectid', 'version',
+ 'testplanid', 'linkid'])
+ def getTestCaseCustomFieldTestPlanDesignValue(self):
+ """ Gets a Custom Field of a Test Case in Test Plan Design Scope. """
+
+
+# /**
+# * Gets a Custom Field of a Test Suite in Design Scope.
+# *
+# * @param struct $args
+# * @param string $args["devKey"]: used to check if operation can be done.
+# * if devKey is not valid => abort.
+# *
+# * @param string $args["customfieldname"]: custom field name
+# * @param int $args["testprojectid"]: project id
+# * @param int $args["testsuiteid"]: test suite id
+# *
+# * @return mixed $resultInfo
+# *
+# * @access public
+# */
+# public function getTestSuiteCustomFieldDesignValue($args)
+
+ @decoMakerApiCallReplaceTLResponseError(replaceValue=None)
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['customfieldname', 'testprojectid',
+ 'testsuiteid'])
+ def getTestSuiteCustomFieldDesignValue(self):
+ """ Gets a Custom Field of a Test Suite in Design Scope."""
+
+# /**
+# * Gets a Custom Field of a Test Plan in Design Scope.
+# *
+# * @param struct $args
+# * @param string $args["devKey"]: used to check if operation can be done.
+# * if devKey is not valid => abort.
+# *
+# * @param string $args["customfieldname"]: custom field name
+# * @param int $args["testprojectid"]: project id
+# * @param int $args["testplanid"]: test plan id
+# *
+# * @return mixed $resultInfo
+# *
+# * @access public
+# */
+# public function getTestPlanCustomFieldDesignValue($args)
+
+ @decoMakerApiCallReplaceTLResponseError(replaceValue=None)
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['customfieldname', 'testprojectid',
+ 'testplanid'])
+ def getTestPlanCustomFieldDesignValue(self):
+ """ Gets a Custom Field of a Test Plan in Design Scope. """
+
+# /**
+# * Gets a Custom Field of a Requirement Specification in Design Scope.
+# *
+# * @param struct $args
+# * @param string $args["devKey"]: used to check if operation can be done.
+# * if devKey is not valid => abort.
+# *
+# * @param string $args["customfieldname"]: custom field name
+# * @param int $args["testprojectid"]: project id
+# * @param int $args["reqspecid"]: requirement specification id
+# *
+# * @return mixed $resultInfo
+# *
+# * @access public
+# */
+# public function getReqSpecCustomFieldDesignValue($args)
+
+ @decoMakerApiCallReplaceTLResponseError(replaceValue=None)
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['customfieldname', 'testprojectid',
+ 'reqspecid'])
+ def getReqSpecCustomFieldDesignValue(self):
+ """ Gets a Custom Field of a Requirement Specification in Design Scope. """
+
+# /**
+# * Gets a Custom Field of a Requirement in Design Scope.
+# *
+# * @param struct $args
+# * @param string $args["devKey"]: used to check if operation can be done.
+# * if devKey is not valid => abort.
+# *
+# * @param string $args["customfieldname"]: custom field name
+# * @param int $args["testprojectid"]: project id
+# * @param int $args["requirementid"]: requirement id
+# *
+# * @return mixed $resultInfo
+# *
+# * @access public
+# */
+# public function getRequirementCustomFieldDesignValue($args)
+
+ @decoMakerApiCallReplaceTLResponseError(replaceValue=None)
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['customfieldname', 'testprojectid',
+ 'requirementid'])
+ def getRequirementCustomFieldDesignValue(self):
+ """ Gets a Custom Field of a Requirement Specification in Design Scope. """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['action', 'steps'],
+ ['testcaseexternalid', 'testcaseid', 'version'])
+ def createTestCaseSteps(self):
+ """ creates new test steps or updates existing test steps
+
+ action - possible values: 'create','update','push'
+ create: if step exist NOTHING WILL BE DONE
+ update: if step DOES NOT EXIST will be created else will be updated.
+ push: NOT IMPLEMENTED YET (TL 1.9.9)
+ shift down all steps with step number >= step number provided
+ and use provided data to create step number requested.
+ steps - each element is a hash with following keys
+ step_number,actions,expected_results,execution_type
+ args variations: testcaseid - testcaseexternalid
+ version - optional if not provided LAST ACTIVE version will be used
+ if all versions are INACTIVE, then latest version will be used.
+ """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testcaseexternalid', 'steps'],
+ ['version'])
+ def deleteTestCaseSteps(self):
+ """ deletes test cases steps
+
+ steps - each element is a step_number
+ version - optional if not provided LAST ACTIVE version will be used
+ """
+
+# /**
+# * Update value of Custom Field with scope='design' for a given Test case
+# *
+# * @param struct $args
+# * @param string $args["devKey"]: used to check if operation can be done.
+# * if devKey is not valid => abort.
+# *
+# * @param string $args["testcaseexternalid"]:
+# * @param string $args["version"]: version number
+# * @param string $args["testprojectid"]:
+# * @param string $args["customfields"] - optional
+# * contains an map with key:Custom Field Name, value: value for CF.
+# * VERY IMPORTANT: value must be formatted in the way it's written to db,
+# * this is important for types like:
+# *
+# * DATE: strtotime()
+# * DATETIME: mktime()
+# * MULTISELECTION LIST / CHECKBOX / RADIO: se multipli selezione ! come separatore
+# *
+# *
+# * these custom fields must be configured to be writte during execution.
+# * If custom field do not meet condition value will not be written
+# *
+# * @return mixed null if everything ok, else array of IXR_Error objects
+# *
+# * @access public
+# */
+# public function updateTestCaseCustomFieldDesignValue($args)
+
+ @decoMakerApiCallReplaceTLResponseError(replaceValue='')
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testcaseexternalid', 'version', 'testprojectid',
+ 'customfields'])
+ def updateTestCaseCustomFieldDesignValue(self):
+ """ Update value of Custom Field with scope='design' for a given Test case
+
+ customfields : dictionary with customfields names + values
+ VERY IMPORTANT: value must be formatted in the way it's written to db """
+
+
+# /**
+# * Update execution type for a test case version
+# *
+# * @param struct $args
+# * @param string $args["devKey"]: used to check if operation can be done.
+# * if devKey is not valid => abort.
+# *
+# * @param string $args["testcaseexternalid"]:
+# * @param string $args["version"]: version number
+# * @param string $args["testprojectid"]:
+# * @param string $args["executiontype"]: TESTCASE_EXECUTION_TYPE_MANUAL,
+# * TESTCASE_EXECUTION_TYPE_AUTOMATIC
+# *
+# * @return mixed null if everything ok, else array of IXR_Error objects
+# *
+# * @access public
+# */
+# public function setTestCaseExecutionType($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testcaseexternalid', 'version', 'testprojectid',
+ 'executiontype'])
+ def setTestCaseExecutionType(self):
+ """ Update execution type for a test case version
+
+ possible executiontype values
+ 1 = TESTCASE_EXECUTION_TYPE_MANUAL, 2 = TESTCASE_EXECUTION_TYPE_AUTO """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid'])
+ def getExecCountersByBuild(self):
+ """ Gets execution metrics information for a testplan """
+ # /**
+ # * create platform
+ # *
+ # * @param struct $args
+ # * @param string $args["devKey"]
+ # * @param string $args["testprojectname"]
+ # * @param string $args["platformname"]
+ # * @param string $args["notes"]
+ # * @param boolean $args["platformondesign"]
+ # * @param boolean $args["platformonexecution"]
+ # * @return mixed $resultInfo
+ # * @internal revisions
+ # */
+ # public function createPlatform($args) {
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectname', 'platformname'],
+ ['notes',
+ 'platformondesign', 'platformonexecution'])
+ def createPlatform(self):
+ """ Creates a platform for test project """
+
+
+ @decoMakerApiCallReplaceTLResponseError(replaceValue={})
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectid'])
+ def getProjectPlatforms(self):
+ """ Gets a dictionary of platforms for a project
+
+ returns an empty dictionary, if no platform is assigned """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid', 'platformname'])
+ def addPlatformToTestPlan(self):
+ """ Adds a platform to a test plan """
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid', 'platformname'])
+ def removePlatformFromTestPlan(self):
+ """ Removes a platform from a test plan """
+
+# /**
+# * if everything ok returns an array on just one element with following user data
+# *
+# * firstName,lastName,emailAddress,locale,isActive,defaultTestprojectID,
+# * globalRoleID
+# * globalRole array with role info
+# * tprojectRoles array
+# * tplanRoles array
+# * login
+# * dbID
+# * loginRegExp
+# *
+# * ATTENTION: userApiKey will be set to NULL, because is worst that access to user password
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param string $args["user"] Login Name
+# *
+# * @return mixed $ret
+# *
+# */
+# public function getUserByLogin($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['user'])
+ def getUserByLogin(self):
+ """ returns user data for account with login name USER
+
+ if everything ok returns an array on just one element with following user data
+ *
+ * firstName,lastName,emailAddress,locale,isActive,defaultTestprojectID,
+ * globalRoleID
+ * globalRole array with role info
+ * tprojectRoles array
+ * tplanRoles array
+ * login
+ * dbID
+ * loginRegExp
+ *
+ * ATTENTION: userApiKey will be set to NULL, because is worst that access to user password """
+
+
+# /**
+# * if everything ok returns an array on just one element with following user data
+# *
+# * firstName,lastName,emailAddress,locale,isActive,defaultTestprojectID,
+# * globalRoleID
+# * globalRole array with role info
+# * tprojectRoles array
+# * tplanRoles array
+# * login
+# * dbID
+# * loginRegExp
+# *
+# * ATTENTION: userApiKey will be set to NULL, because is worst that access to user password
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param string $args["userid"] user ID as present on users table, column ID
+# *
+# * @return mixed $ret
+# *
+# */
+# public function getUserByID($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['userid'])
+ def getUserByID(self):
+ """ returns user data for account with USERID in users table, column ID
+
+ * if everything ok returns an array on just one element with following user data
+ *
+ * firstName,lastName,emailAddress,locale,isActive,defaultTestprojectID,
+ * globalRoleID
+ * globalRole array with role info
+ * tprojectRoles array
+ * tplanRoles array
+ * login
+ * dbID
+ * loginRegExp
+ *
+ * ATTENTION: userApiKey will be set to NULL, because is worst that access to user password
+ """
+
+# /**
+# * Update an existing test case
+# * Not all test case attributes will be able to be updated using this method
+# * See details below
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param string $args["testcaseexternalid"] format PREFIX-NUMBER
+# * @param int $args["version"] optional version NUMBER (human readable)
+# * @param string $args["testcasename"] - optional
+# * @param string $args["summary"] - optional
+# * @param string $args["preconditions"] - optional
+# * @param array $args["steps"] - optional
+# * each element is a hash with following keys
+# * step_number,actions,expected_results,execution_type
+# *
+# * @param int $args["importance"] - optional - see const.inc.php for domain
+# * @param int $args["executiontype"] - optional - see ... for domain
+# * @param int $args["status'] - optional
+# * @param int $args["estimatedexecduration'] - optional
+# * @param string $args["user'] - login name used as updater - optional
+# * if not provided will be set to user that request update
+# */
+# public function updateTestCase($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testcaseexternalid'],
+ ['version', 'testcasename','summary', 'preconditions', 'steps',
+ 'importance', 'executiontype', 'status', 'estimatedexecduration',
+ 'user'])
+ def updateTestCase(self):
+ """ Update an existing test case
+
+ steps array - each element is a hash with following keys
+ step_number,actions,expected_results,execution_type
+ user login name used as updater - optional
+ if not provided will be set to user that request update
+
+ Not all test case attributes will be able to be updated using this method
+ """
+
+ def testLinkVersion(self):
+ """ Returns the TestLink Version
+ usable with TL>= 1.9.9 , returns '<= 1.9.8' for older versions """
+
+ tl_version = '<= 1.9.8'
+ try:
+ tl_version = self.callServerWithPosArgs('testLinkVersion')
+ except testlinkerrors.TLAPIError:
+ # TL does not know this api method, version must be < 1.9.9
+ pass
+ return tl_version
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['user', 'testplanid', 'testcaseexternalid'],
+ ['buildid', 'buildname', 'platformid', 'platformname'])
+ def assignTestCaseExecutionTask(self):
+ """ assigns a user to a test case execution task
+
+ user login name => tester
+ testplanid test plan id
+ testcaseexternalid format PREFIX-NUMBER
+
+ args variations: buildid - buildname
+ platformid - platformname
+ build information is general mandatory
+ platform information is required, when test plan has assigned platforms
+ """
+
+# /**
+# * Returns all bugs linked to a particular testcase on a test plan.
+# * If there are no filter criteria regarding platform and build,
+# * result will be get WITHOUT checking for a particular platform and build.
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["tplanid"]
+# * @param int $args["testcaseid"]: Pseudo optional.
+# * if does not is present then testcaseexternalid MUST BE present
+# *
+# * @param int $args["testcaseexternalid"]: Pseudo optional.
+# * if does not is present then testcaseid MUST BE present
+# *
+# * @param string $args["platformid"]: optional.
+# * ONLY if not present, then $args["platformname"]
+# * will be analized (if exists)
+# *
+# * @param string $args["platformname"]: optional (see $args["platformid"])
+# *
+# * @param int $args["buildid"]: optional
+# * ONLY if not present, then $args["buildname"] will be analized (if exists)
+# *
+# * @param int $args["buildname"] - optional (see $args["buildid"])
+# *
+# *
+# * @return mixed $resultInfo
+# * if execution found
+# * array that contains a map with these keys:
+# * bugs
+# *
+# * if test case has not been execute,
+# * array('id' => -1)
+# *
+# * @access public
+# */
+# public function getTestCaseBugs($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid' ],
+ ['testcaseid', 'testcaseexternalid',
+ 'buildid', 'buildname', 'platformid', 'platformname'])
+ def getTestCaseBugs(self):
+ """ Returns all bugs linked to a particular testcase on a test plan.
+ If there are no filter criteria regarding platform and build,
+ result will be get WITHOUT checking for a particular platform and build.
+
+
+ testplanid test plan id
+
+ args variations: testcaseid - testcaseexternalid (mandatory!)
+ buildid - buildname
+ platformid - platformname
+ test case information is general mandatory
+ """
+
+# /**
+# * Gets the result of LAST EXECUTION for a particular testcase on a test plan.
+# * If there are no filter criteria regarding platform and build,
+# * result will be get WITHOUT checking for a particular platform and build.
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["tplanid"]
+# * @param string $args["testcaseexternalid"] format PREFIX-NUMBER
+# * @param int $args["buildid"] Mandatory => you can provide buildname as alternative
+# * @param int $args["buildname"] Mandatory => you can provide buildid (DB ID) as alternative
+# * @param int $args["platformid"] optional - BECOMES MANDATORY if Test plan has platforms
+# * you can provide platformname as alternative
+# *
+# * @param int $args["platformname"] optional - BECOMES MANDATORY if Test plan has platforms
+# * you can provide platformid as alternative
+# *
+# *
+# * @return mixed $resultInfo
+# *
+# * @access public
+# */
+# public function getTestCaseAssignedTester($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid', 'testcaseexternalid'],
+ ['buildid', 'buildname', 'platformid', 'platformname'])
+ def getTestCaseAssignedTester(self):
+ """ Gets the result of LAST EXECUTION for a particular testcase on a
+ test plan.
+
+ testplanid test plan id
+ testcaseexternalid format PREFIX-NUMBER
+
+ args variations: buildid - buildname
+ platformid - platformname
+ build information is general mandatory
+ platform information is required, when test plan has assigned platforms
+ """
+# /**
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["testplanid"]
+# * @param string $args["testcaseexternalid"] format PREFIX-NUMBER
+# * @param int $args["buildid"] Mandatory => you can provide buildname as alternative
+# * @param int $args["buildname"] Mandatory => you can provide buildid (DB ID) as alternative
+# * @param int $args["platformid"] optional - BECOMES MANDATORY if Test plan has platforms
+# * you can provide platformname as alternative
+# *
+# * @param int $args["platformname"] optional - BECOMES MANDATORY if Test plan has platforms
+# * you can provide platformid as alternative
+# * @param string $args["user'] - login name => tester
+# * - NOT NEEDED f $args['action'] = 'unassignAll'
+# * ¸
+# *
+# */
+# public function unassignTestCaseExecutionTask($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid', 'testcaseexternalid'],
+ ['buildid', 'buildname', 'platformid', 'platformname',
+ 'user', 'action'])
+ def unassignTestCaseExecutionTask(self):
+ """ assigns a user to a test case execution task
+
+ testplanid test plan id
+ testcaseexternalid format PREFIX-NUMBER
+
+ args variations: buildid - buildname
+ platformid - platformname
+ user (login name) - action ('unassignAll')
+ build information is general mandatory
+ platform information is required, when test plan has assigned platforms
+ if action=='unassignAll', user information is not needed
+ - otherwise, TL itself will set action to 'unassignOne' and expects a
+ valid user information (login name => tester)
+
+ """
+
+ @decoMakerApiCallReplaceTLResponseError(replaceValue={})
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectid'])
+ def getProjectKeywords(self):
+ """ Gets a dictionary of valid keywords for a project
+
+ returns an empty dictionary, if no keywords are defined """
+
+
+# # /**
+# # * Gets list of keywords for a given Test case
+# # *
+# # * @param $testcaseid
+# # *
+# # * @return map indexed by bug_id
+# # *
+# # * @access public
+# # */
+# # public function getTestCaseKeywords($args)
+#
+ @decoMakerApiCallReplaceTLResponseError(replaceValue={})
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs([], ['testcaseid', 'testcaseexternalid'])
+ def getTestCaseKeywords(self):
+ """ Gets a dictionary of keywords for a given Test case
+
+ args variations: testcaseid - testcaseexternalid (mandatoy!)
+
+ returns an empty dictionary, if no keywords are defined """
+
+# /**
+# * Delete a test plan and all related link to other items
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["$tplanID"]
+# *
+# * @return mixed $resultInfo
+# * [status] => true/false of success
+# * [id] => result id or error code
+# * [message] => optional message for error message string
+# * @access public
+# */
+# public function deleteTestPlan($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid'])
+ def deleteTestPlan(self):
+ """ Delete a test plan and all related link to other items """
+
+ #
+ # public methods for general server calls
+ #
+
+ def callServerWithPosArgs(self, methodNameAPI, *argsPositional, **argsOptional):
+ """ concat argsPositional and argsOptional before calling
+ server method methodNameAPI """
+
+ if argsPositional:
+ # search keys for values and store these pairs in a dictionary
+ dictPos = self._convertPostionalArgs(methodNameAPI, argsPositional)
+ # extent optional keys+values with positional keys+vales
+ argsOptional.update(dictPos)
+ # now, start calling the server with basic error handling
+ response = self._callServer(methodNameAPI, argsOptional)
+ # check if response is not empyt and not includes error code
+ self._checkResponse(response, methodNameAPI, argsOptional)
+ # seams to be ok, so let give them the data
+ return response
+
+# /**
+# * addTestCaseKeywords
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param array array $args["keywords"]: map key testcaseexternalid
+# * values array of keyword name
+# * @return mixed $resultInfo
+# *
+# * call examples:
+# * c$args=array();
+# * c$args["devKey"]=isset($_REQUEST['apiKey']) ? $_REQUEST['apiKey'] : $devKey;
+# * c$args["keywords"] = array('MAB-3' => array('Barbie','Barbie'),
+# 'MAB-2' => array('Barbie','Jessie'));
+# *
+# * @internal revisions
+# * @since 1.9.13, interface changed in 1.9.14
+# */
+# function addTestCaseKeywords($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['keywords'], [])
+ def addTestCaseKeywords(self):
+ """ adds list of keywords to a set of test cases
+
+ expects as arg a dictionary with
+ as a key and as value
+
+ example:
+ {'TC-4711' : ['KeyWord02'], 'TC-4712' : ['KeyWord01', KeyWord03']}
+
+ adds to test case 'TC-4711' the keyword 'KeyWord02'
+ adds to test case 'TC-4712' the keywords 'KeyWord01' + KeyWord03'
+ """
+
+# /**
+# * removeTestCaseKeywords
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param array array $args["keywords"]: map key testcaseexternalid
+# * values array of keyword name
+# * @return mixed $resultInfo
+# *
+# * call examples:
+# * c$args=array();
+# * c$args["devKey"]=isset($_REQUEST['apiKey']) ? $_REQUEST['apiKey'] : $devKey;
+# * c$args["keywords"] = array('MAB-3' => array('Barbie','Barbie'),
+# 'MAB-2' => array('Barbie','Jessie'));
+# *
+# * @internal revisions
+# * @since 1.9.13, interface changed in 1.9.14
+# */
+# function removeTestCaseKeywords($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['keywords'], [])
+ def removeTestCaseKeywords(self):
+ """ removes list of keywords from a set of test cases
+
+ expects as arg a dictionary with
+ as a key and as value
+
+ example:
+ {'TC-4711' : ['KeyWord02'], 'TC-4712' : ['KeyWord01', KeyWord03']}
+
+ removes from test case 'TC-4711' the keyword 'KeyWord02'
+ removes from test case 'TC-4712' the keywords 'KeyWord01' + KeyWord03'
+ """
+
+# /**
+# * Delete a test project and all related link to other items
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["prefix"]
+# *
+# * @return mixed $resultInfo
+# * [status] => true/false of success
+# * [message] => optional message for error message string
+# * @access public
+# */
+# public function deleteTestProject($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['prefix'], [])
+ def deleteTestProject(self):
+ """ Delete a test project and all related link to other items """
+
+# /**
+# * Update value of Custom Field with scope='design'
+# * for a given Test Suite
+# *
+# * @param struct $args
+# * @param string $args["devKey"]: used to check if operation can be done.
+# * if devKey is not valid => abort.
+# *
+# * @param string $args["testsuiteid"]:
+# * @param string $args["testprojectid"]:
+# * @param string $args["customfields"]
+# * contains an map with key:Custom Field Name, value: value for CF.
+# * VERY IMPORTANT: value must be formatted in the way it's written to db,
+# * this is important for types like:
+# *
+# * DATE: strtotime()
+# * DATETIME: mktime()
+# * MULTISELECTION LIST / CHECKBOX / RADIO: se multipli selezione ! come separatore
+# *
+# *
+# * these custom fields must be configured to be writte during execution.
+# * If custom field do not meet condition value will not be written
+# *
+# * @return mixed null if everything ok, else array of IXR_Error objects
+# *
+# * @access public
+# */
+# public function updateTestSuiteCustomFieldDesignValue($args)
+
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectid', 'testsuiteid', 'customfields'])
+ def updateTestSuiteCustomFieldDesignValue(self):
+ """ Update value of Custom Field with scope='design' for a given Test Suite
+
+ customfields : dictionary with customfields names + values
+ VERY IMPORTANT: value must be formatted in the way it's written to db
+ """
+
+# /**
+# * Returns all test suites inside target
+# * test project with target name
+# *
+# * @param
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["testsuitename"]
+# * @param string $args["prefix"]
+# * @return mixed $resultInfo
+# *
+# * @access public
+# */
+# public function getTestSuite($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testsuitename', 'prefix'])
+ def getTestSuite(self):
+ """ Returns list with all test suites named TESTUITENAME defined for
+ test project using PREFIX """
+
+
+# /**
+# * update a test suite
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["testprojectid"] OR string $args["prefix"]
+# * @param string $args["testsuitename"] optional
+# * @param string $args["details"] optional
+# * @param int $args["parentid"] optional, if do not provided means test suite must be top level.
+# * @param int $args["order"] optional. Order inside parent container
+# *
+# * @return mixed $resultInfo
+# */
+# public function updateTestSuite($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testsuiteid'],
+ ['testprojectid', 'prefix', 'parentid', 'testsuitename', 'details',
+ 'order'])
+ def updateTestSuite(self):
+ """ update a test suite
+
+ mandatory arg: testsuiteid - identifies the test suite to be change
+
+ mandatory args variations: testprojectid or prefix
+ - test project information is general mandatory
+
+ optional args:
+ - testsuitename - if defined, test suite name will be changed
+ - details - if defined test suite details will be changed
+ - order - if defined, order inside parent container is changed
+ """
+
+# /**
+# * Get Issue Tracker System by name
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param string $args["itsname"] ITS name
+# * @return mixed $itsObject
+# * @access public
+# */
+# public function getIssueTrackerSystem($args,$call=null)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['itsname'], [])
+ def getIssueTrackerSystem(self):
+ """ Get Issue Tracker System by name """
+
+# /**
+# * Update value of Custom Field with scope='design'
+# * for a given Build
+# *
+# * @param struct $args
+# * @param string $args["devKey"]: used to check if operation can be done.
+# * if devKey is not valid => abort.
+# *
+# * @param string $args["buildid"]:
+# * @param string $args["testprojectid"]:
+# * @param string $args["customfields"]
+# * contains an map with key:Custom Field Name, value: value for CF.
+# * VERY IMPORTANT: value must be formatted in the way it's written to db,
+# * this is important for types like:
+# *
+# * DATE: strtotime()
+# * DATETIME: mktime()
+# * MULTISELECTION LIST / CHECKBOX / RADIO: se multipli selezione ! come separatore
+# *
+# *
+# * these custom fields must be configured to be writte during execution.
+# * If custom field do not meet condition value will not be written
+# *
+# * @return mixed null if everything ok, else array of IXR_Error objects
+# *
+# * @access public
+# */
+# public function updateBuildCustomFieldsValues($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectid', 'testplanid', 'buildid',
+ 'customfields'])
+ def updateBuildCustomFieldsValues(self):
+ """ Update value of Custom Field with scope='design' for a given Build
+
+ customfields : dictionary with customfields names + values
+ VERY IMPORTANT: value must be formatted in the way it's written to db
+ """
+
+# /**
+# * Gets a set of EXECUTIONS for a particular testcase on a test plan.
+# * If there are no filter criteria regarding platform and build,
+# * result will be get WITHOUT checking for a particular platform and build.
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["tplanid"]
+# * @param int $args["testcaseid"]: Pseudo optional.
+# * if is not present then testcaseexternalid MUST BE present
+# *
+# * @param int $args["testcaseexternalid"]: Pseudo optional.
+# * if is not present then testcaseid MUST BE present
+# *
+# * @param string $args["platformid"]: optional.
+# * ONLY if not present, then $args["platformname"]
+# * will be analized (if exists)
+# *
+# * @param string $args["platformname"]: optional (see $args["platformid"])
+# * @param int $args["buildid"]: optional
+# * ONLY if not present, $args["buildname"] will be analized (if exists)
+# *
+# * @param int $args["buildname"] - optional (see $args["buildid"])
+# * @param int $args["options"] - optional
+# * options['getOrderDescending']
+# * false(=ascending,default)
+# * @return mixed $resultInfo
+# * if execution found
+# * array that contains a map with these keys:
+# * id (execution id),build_id,tester_id,execution_ts,
+# * status,testplan_id,tcversion_id,tcversion_number,
+# * execution_type,notes.
+# *
+# * if test case has not been executed,
+# * array('id' => -1)
+# * @access public
+# */
+# public function getExecutionSet($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid'],
+ ['testcaseid', 'testcaseexternalid',
+ 'buildid', 'buildname', 'platformid', 'platformname',
+ 'options'])
+ def getExecutionSet(self):
+ """ Gets a set of EXECUTIONS for a particular testcase on a test plan.
+ If there are no filter criteria regarding platform and build, result
+ will be get WITHOUT checking for a particular platform and build.
+
+ mandatory arg: testplanid - identifies the test plan
+
+ mandatory args variations: testcaseid - testcaseexternalid
+ - test case information is general mandatory
+
+ optional args variations: buildid - buildname
+ platformid - platformname
+
+ options : dictionary with key 'getOrderDescending' and
+ values 0 (false = default) or 1 (true)
+ """
+
+# /**
+# * Get requirements
+# *
+# * @param string $args["testprojectid"]
+# * @param string $args["testplanid"] OPTIONAL
+# * @param string $args["platformid"] OPTIONAL
+# *
+# * @return mixed error if someting's wrong, else array of test cases
+# *
+# * @access public
+# */
+# public function getRequirements($args)
+
+ @decoMakerApiCallReplaceTLResponseError()
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectid'], ['testplanid', 'platformid'])
+ def getRequirements(self):
+ """ Get requirements.
+
+ mandatory arg: testprojectid - identifies the test project
+
+ optional args: testplanid, platformid
+ """
+
+# /**
+# * Get requirement coverage
+# *
+# * Retrieve the test cases associated to a requirement
+# *
+# * @param struct $args
+# * @param string $args["devKey"]: used to check if operation can be done.
+# * if devKey is not valid => abort.
+# *
+# * @param string $args["testprojectid"]
+# * @param string $args["requirementdocid"]
+# *
+# * @return mixed error if someting's wrong, else array of test cases
+# *
+# * @access public
+# */
+# public function getReqCoverage($args)
+
+ @decoMakerApiCallReplaceTLResponseError()
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testprojectid', 'requirementdocid'], [])
+ def getReqCoverage(self):
+ """ Get requirement coverage.
+ Retrieve the test cases associated to a requirement
+
+ mandatory arg:
+ testprojectid - identifies the test project
+ requirementdocid - identifies the requirement
+
+ """
+
+# /**
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param string $args["testcaseexternalid"] format PREFIX-NUMBER
+# * @param int $args["testsuiteid"]
+# *
+# */
+# public function setTestCaseTestSuite($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testcaseexternalid', 'testsuiteid'], [])
+ def setTestCaseTestSuite(self):
+ """ move a test case to a different Test Suite
+
+ mandatory arg:
+ testcaseexternalid - identifies the test case
+ testsuiteid - identifies the test suite
+
+ """
+
+# /**
+# * Gets attachments for specified test suite.
+# * The attachment file content is Base64 encoded. To save the file to disk in client,
+# * Base64 decode the content and write file in binary mode.
+# *
+# * @param struct $args
+# * @param string $args["devKey"] Developer key
+# * @param int $args["testsuiteid"]: id of the testsuite
+# *
+# * @return mixed $resultInfo
+# * @author dennis@etern-it.de
+# */
+# public function getTestSuiteAttachments($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testsuiteid'], [])
+ def getTestSuiteAttachments(self):
+ """ Gets attachments for specified test suite.
+ The attachment file content is Base64 encoded. To save the file to disk
+ in client, Base64 decode the content and write file in binary mode. """
+
+# /**
+# * Gets ALL EXECUTIONS for a particular testcase on a test plan.
+# * If there are no filter criteria regarding platform and build,
+# * result will be get WITHOUT checking for a particular platform and build.
+# *
+# * @param struct $args
+# * @param string $args["devKey"]
+# * @param int $args["tplanid"]
+# * @param int $args["testcaseid"]: Pseudo optional.
+# * if does not is present then testcaseexternalid MUST BE present
+# *
+# * @param int $args["testcaseexternalid"]: Pseudo optional.
+# * if does not is present then testcaseid MUST BE present
+# *
+# * @param string $args["platform_id"]: optional.
+# * will be analized ONLY if present and exists
+# *
+# * @param int $args["build_id"]: optional
+# * will be analized ONLY if present and exists
+# *
+# * @param int $args["options"] - optional
+# * options['getBugs'] = true / false
+# *
+# *
+# * @return mixed $resultInfo
+# * if executions found
+# * array that contains a map for each execution with these keys:
+# * id (execution id),build_id,tester_id,execution_ts,
+# * status,testplan_id,tcversion_id,tcversion_number,
+# * execution_type,notes.
+# *
+# * If user has requested getbugs, then a key bugs (that is an array)
+# * will also exists.
+# *
+# * if test case has not been execute, the first map will be returned with -1 as 'id'
+# *
+# * @access public
+# */
+# public function getAllExecutionsResults($args)
+
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['testplanid'],
+ ['testcaseid', 'testcaseexternalid',
+ 'platformid', 'buildid', 'options'])
+ def getAllExecutionsResults(self):
+ """ Gets ALL EXECUTIONS for a particular testcase on a test plan.
+ If there are no filter criteria regarding platform and build,
+ result will be get WITHOUT checking for a particular platform and build.
+
+ mandatory arg: testplanid - identifies the test plan
+
+ mandatory args variations: testcaseid - testcaseexternalid
+ - test case information is general mandatory
+
+ optional args: buildid
+ platformid
+
+ options : dictionary with key 'getBugs' and
+ values 0 (false = default) or 1 (true)
+ """
+
+ # /**
+ # * Create a new user
+ # *
+ # * Restricted to site admin
+ # *
+ # * @param struct $args
+ # * @param string $args["devKey"]
+ # * @param string $args["login"]
+ # * @param string $args["firstname"]
+ # * @param string $args["lastname"]
+ # * @param string $args["email"]
+ # * @param string $args["password"] - OPTIONAL
+ # *
+ # *
+ # * @return ID the new user if OK, otherwise error structure
+ # *
+ # * @access public
+ # */
+ # public function createUser($args) {
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['login', 'firstname', 'lastname', 'email'],
+ ['password'])
+ def createUser(self):
+ """ Create a new user """
+
+ # /**
+ # * Set a role to a user at project level
+ # *
+ # * Restricted to users with System Wide Role Admin
+ # *
+ # * @param struct $args
+ # * @param struct $args["userid"]
+ # * @param struct $args["rolename"]
+ # * @param struct $args["testprojectid"]
+ # *
+ # * @return true if OK, otherwise error structure
+ # *
+ # * @access public
+ # */
+ # public function setUserRoleOnProject($args)
+ @decoApiCallAddDevKey
+ @decoMakerApiCallWithArgs(['userid', 'rolename', 'testprojectid'])
+ def setUserRoleOnProject(self):
+ """ Set a role to a user at project level
+ Restricted to users with System Wide Role Admin
+ """
+
+ #
+ # internal methods for general server calls
+ #
+
+ def _callServer(self, methodNameAPI, argsAPI=None):
+ """ call server method METHODNAMEAPI with error handling and
+ returns the responds
+ internal method - should not be called directly """
+
+ response = None
+ try:
+ if argsAPI is None:
+ response = getattr(self.server.tl, methodNameAPI)()
+ else:
+ response = getattr(self.server.tl, methodNameAPI)(argsAPI)
+ except (IOError, xmlrpc_client.ProtocolError) as msg:
+ new_msg = 'problems connecting the TestLink Server %s\n%s' %\
+ (self._server_url, msg)
+ raise testlinkerrors.TLConnectionError(new_msg)
+ except xmlrpc_client.Fault as msg:
+ new_msg = 'problems calling the API method %s\n%s' %\
+ (methodNameAPI, msg)
+ raise testlinkerrors.TLAPIError(new_msg)
+
+ return response
+
+
+ def _convertPostionalArgs(self, methodName, valueList):
+ """ Returns a dictionary with values from VALUELIST and keys for
+ the expected positional argumenst of selfs method METHODNAME
+
+ if VALUELIST does not match the expectation, an error
+ testlinkerrors.TLArgError is raised """
+
+ if not methodName in self._positionalArgNames:
+ new_msg = '%s - missing positional args configuration' %\
+ (methodName)
+ raise testlinkerrors.TLArgError(new_msg)
+
+ nameList = self._positionalArgNames[methodName]
+ length_NameList = len(nameList)
+ length_ValueList = len(valueList)
+
+ if length_NameList != length_ValueList:
+ new_msg = '%s - mismatching number of positional args %i vs %i' %\
+ (methodName, length_NameList, length_ValueList)
+ new_msg = '%s\n expected args: %s' % (new_msg, ', '.join(nameList))
+ raise testlinkerrors.TLArgError(new_msg)
+
+ # issue #20: Following line works with Py27, but not with Py26
+ # return {nameList[x] : valueList[x] for x in range(len(nameList)) }
+ # this line works with Py26 and Py27 (and is also nice)
+ return dict(list(zip(nameList, valueList)))
+
+ def _getAttachmentArgs(self, attachmentfile):
+ """ returns dictionary with key/value pairs needed, to transfer
+ ATTACHMENTFILE via the api to into TL
+
+ ATTACHMENTFILE could be:
+ a) a python file descriptor pointing to the file
+ b) a valid file path"""
+
+ try:
+ # try to handle ATTACHMENTFILE as a file path
+ a_file_path = attachmentfile
+ a_file_obj = self._openAttachmentForRead(a_file_path)
+ already_file_obj = False
+ except TypeError:
+ # ATTACHMENTFILE seams to be a file object
+ a_file_path = attachmentfile.name
+ a_file_obj = attachmentfile
+ already_file_obj = True
+
+ try:
+ encoded_data = encodebytes(a_file_obj.read())
+ except TypeError:
+ # a_file_obj seams to have a wrong read mode
+ # try to reopen it, if ATTACHMENTFILE already was a file obj
+ if already_file_obj:
+ encoded_data = self._getAttachmentArgs(attachmentfile.name)
+ else:
+ raise testlinkerrors.TLArgError(
+ 'invalid attachment file: %s' % attachmentfile)
+ finally:
+ # ensure file open inside this method call is closed again
+ if not already_file_obj:
+ a_file_obj.close()
+
+ return {'filename':os.path.basename(a_file_path),
+ 'filetype':guess_type(a_file_path)[0],
+ 'content':encoded_data
+ }
+
+ def _openAttachmentForRead(self, a_file_path):
+ """ open A_FILE_PATH for reading and returns the file descriptor.
+ Read mode will be set depending from py version and mimetype
+ PY2: text file = 'r', others = 'rb' PY3: general 'rb'
+
+ Raise TLArgError, if A_FILE_PATH is not valid
+
+ site effect: raise TypeError, if A_FILE_PATH is not a string
+ """
+
+ if not os.path.exists(a_file_path):
+ # file path does not exists
+ raise testlinkerrors.TLArgError(
+ 'invalid attachment path: %s' % a_file_path)
+
+ a_read_mode = 'rb'
+ is_text_file = 'text/' in guess_type(a_file_path)
+ if not IS_PY3 and is_text_file:
+ # under py2 text file shpuld be open as 'r' and not 'rb'
+ # for details compare py2 and py docs
+ # https://docs.python.org/2/library/base64.html#base64.encodestring
+ # https://docs.python.org/3/library/base64.html#base64.encodebytes
+ a_read_mode = 'r'
+ return open(a_file_path, a_read_mode)
+
+
+ def _checkResponse(self, response, methodNameAPI, argsOptional):
+ """ Checks if RESPONSE is empty or includes Error Messages
+ Will raise TLRepsonseError in this case """
+ if response:
+ try:
+ if 'code' in response[0]:
+ raise testlinkerrors.TLResponseError(
+ methodNameAPI, argsOptional,
+ response[0]['message'], response[0]['code'])
+ except (TypeError, KeyError):
+ # if the reponse has not a [{..}] structure, the check
+ # 'code' in response[0]
+ # raise an error. Following causes are ok
+ # TypeError: raised from doesUserExist, cause the postiv
+ # response is simply 'True'
+ # KeyError: raise from uploadExecutionAttachment, cause the
+ # positiv response is directly a dictionary
+ pass
+ else:
+ raise testlinkerrors.TLResponseError(methodNameAPI, argsOptional,
+ 'Empty Response! ')
+
+
+
+
+
+ #
+ # ADDITIONNAL FUNCTIONS
+ #
+
+ def _apiMethodArgNames(self, methodNameAPI):
+ """ returns triple with arg name lists for api METHODNAME
+ 1. positional api arg names
+ 2. optional api arg names
+ 3. other (non api) name
+ """
+ # collect arg names
+ posArgNames = self._positionalArgNames.get(methodNameAPI, [])
+ otherArgs = ([],[])
+ try:
+ otherArgs = getArgsForMethod(methodNameAPI, posArgNames)
+ except testlinkerrors.TLArgError:
+ # no API args registered for methodName
+ pass
+ return (posArgNames, otherArgs[0], otherArgs[1])
+
+ def whatArgs(self, methodNameAPI):
+ """ returns for METHODNAME a description with
+ - positional, optional and other (non api) mandatory args
+ - methods doc/help string
+ """
+
+ # collect arg names
+ (posArgNames, optArgNames, manArgNames) = \
+ self._apiMethodArgNames(methodNameAPI)
+
+ # get method doc string
+ ownMethod = True
+ docString = None
+ argSeparator = ''
+ try:
+ apiMethod = self.__getattribute__(methodNameAPI)
+ docString = apiMethod.__doc__
+ except AttributeError:
+ # no real method defined for methodNameAPI
+ ownMethod = False
+
+ # now we start to build the description
+ # first the method name
+ methDescr = ''
+ if not ownMethod:
+ methDescr = "callServerWithPosArgs('%s'" % methodNameAPI
+ argSeparator = ', '
+ if not optArgNames:
+ optArgNames = ['apiArg']
+ else:
+ methDescr = "%s(" % methodNameAPI
+
+ # description pos and mandatory args
+ manArgNames.extend(posArgNames)
+ if manArgNames:
+ tmp_l = ['<%s>' % x for x in manArgNames]
+ methDescr += '%s%s' % (argSeparator, ", ".join(tmp_l))
+ argSeparator = ', '
+ # description optional args
+ if optArgNames:
+ tmp_l = ['%s=<%s>' % (x,x) for x in optArgNames]
+ methDescr += '%s[%s]' % (argSeparator, "], [".join(tmp_l))
+
+ # closing the method call
+ methDescr += ")"
+
+ # now append methods docstring
+ if docString:
+ methDescr += "\n%s" % docString
+
+ return methDescr
+
+ def connectionInfo(self):
+ """ print current SERVER URL and DEVKEY settings and servers VERSION """
+
+ tl_version = ''
+ tl_about = ''
+ try:
+ tl_version = self.testLinkVersion()
+ tl_about = self.about()
+ except testlinkerrors.TLConnectionError as msg:
+ tl_version = msg
+
+ message = """
+Current connection settings
+ Server URL: %s
+ DevKey : %s
+Server informations
+ Version : %s
+%s
+"""
+ return message % (self._server_url, self.devKey, tl_version, tl_about)
+
+ def __str__(self):
+ message = """
+TestLink API - class %s - version %s (PY %s)
+@authors: %s
+%s
+"""
+ return message % (self.__class__.__name__, self.__version__,
+ python_version(),
+ self.__author__, self.connectionInfo())
+
+
+if __name__ == "__main__":
+ tl_helper = TestLinkHelper()
+ tl_helper.setParamsFromArgs()
+ myTestLink = tl_helper.connect(TestlinkAPIGeneric)
+ print(myTestLink)
+
+
+
+
diff --git a/src/testlink/testlinkargs.py b/src/testlink/testlinkargs.py
new file mode 100644
index 0000000..bf40bb7
--- /dev/null
+++ b/src/testlink/testlinkargs.py
@@ -0,0 +1,120 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+from .testlinkerrors import TLArgError
+
+
+__doc__ = """ This internal module is used as a 'singleton' to register the
+supported TestLink API methods and there (positional and optional) arguments """
+
+
+# main hash, where the registered methods and there arguments are stored
+# PLEASE never manipulate the entries directly.
+#
+# definitions structure is
+# key(apiMethodeName) = ( [default positional apiArgs], [all apiArgs],
+# [other mandatory non api Args] )
+# [all apiArgs] includes all positional and optional args without other
+# mandatory Args
+_apiMethodsArgs = {}
+
+
+def _resetRegister():
+ """ clears all entries in _apiMethodsArgs"""
+ _apiMethodsArgs.clear()
+
+
+def _getMethodsArgDefinition(methodName):
+ """ returns argument definition for api methodName """
+
+ try:
+ return _apiMethodsArgs[methodName]
+ except KeyError:
+ raise TLArgError('apiMethod %s not registered!' % methodName)
+
+
+def registerMethod(methodName, apiArgsPositional=[], apiArgsOptional=[],
+ otherArgsMandatory=[]):
+ """ extend _apiMethodsArgs with a new definition structure for METHODNAME
+
+ definitions structure is
+ key(apiMethodeName) = ( [default positional apiArgs], [all apiArgs],
+ [other mandatory non api Args] )
+ [all apiArgs] includes all positional and optional args without other
+ mandatory Args """
+
+ if methodName in _apiMethodsArgs:
+ raise TLArgError('apiMethod %s already registered!' % methodName)
+
+ allArgs = apiArgsPositional[:]
+ for argName in apiArgsOptional:
+ if not argName in allArgs:
+ allArgs.append(argName)
+ _apiMethodsArgs[methodName] = (apiArgsPositional[:], allArgs,
+ otherArgsMandatory[:])
+
+
+def registerArgOptional(methodName, argName):
+ """ Update _apiMethodsArgs[methodName] with additional optional argument """
+
+ allArgs = _getMethodsArgDefinition(methodName)[1]
+ if not argName in allArgs:
+ allArgs.append(argName)
+
+
+def registerArgNonApi(methodName, argName):
+ """ Update _apiMethodsArgs[methodName] with additional non api argument """
+
+ nonApiArgs = _getMethodsArgDefinition(methodName)[2]
+ if not argName in nonApiArgs:
+ nonApiArgs.append(argName)
+
+
+def getMethodsWithPositionalArgs():
+ """ returns a dictionary with method names and there positional args """
+ positionalArgNames = {}
+ for mname, argdef in list(_apiMethodsArgs.items()):
+ # does method MNAME has defined positional arguments?
+ if argdef[0]:
+ positionalArgNames[mname] = argdef[0][:]
+ return positionalArgNames
+
+# def getApiArgsForMethod(methodName):
+# """ returns list with all api argument name for METHODNAME """
+# return _getMethodsArgDefinition(methodName)[1][:]
+
+
+def getArgsForMethod(methodName, knownArgNames=[]):
+ """ returns for METHODNAME additional arg names as a tuple with two lists
+ a) optional api arguments, not listed in knownArgNames
+ b) additional mandatory non api arguments
+
+ raise TLArgError, if METHODNAME is not registered """
+
+ # argument definitions in _apiMethodsArgs
+ argDef = _getMethodsArgDefinition(methodName)
+
+ # find missing optional arg names
+ apiArgsAll = argDef[1]
+ apiArgs = [x for x in apiArgsAll if x not in knownArgNames]
+
+ # other mandatory arg names
+ manArgs = argDef[2][:]
+
+ return (apiArgs, manArgs)
diff --git a/src/testlink/testlinkdecorators.py b/src/testlink/testlinkdecorators.py
new file mode 100644
index 0000000..0027c06
--- /dev/null
+++ b/src/testlink/testlinkdecorators.py
@@ -0,0 +1,191 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2013-2019 Luiko Czub, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+from functools import wraps
+from .testlinkargs import registerMethod, registerArgOptional, registerArgNonApi
+from .testlinkerrors import TLResponseError
+
+__doc__ = """ This internal module defines the decorator functions, which are
+used to generate the TestLink API methods in a generic way
+
+Method definitions should be build either with
+@decoMakerApiCallWithArgs(argNamesPositional, argNamesOptional)
+ for calling a server method with arguments
+ - argNamesPositional = list default positional args
+ - argNamesOptional = list additional optional args
+ to check the server response, if it includes TestLink Error Codes or
+ an empty result (which raise a TLResponseError)
+or
+@decoApiCallWithoutArgs
+ for calling server methods without arguments
+ to check the server response, if it includes TestLink Error Codes or
+ an empty result (which raise a TLResponseError)
+
+Additional behavior could be added with
+
+@decoApiCallAddDevKey
+ - to expand the parameter list with devKey key/value pair
+ before calling the server method
+@decoMakerApiCallReplaceTLResponseError(replaceCode)
+ - to catch an TLResponseError after calling the server method and with an
+ empty list
+ - replaceCode : TestLink Error Code, which should be handled
+ default is None for "Empty Results"
+@decoApiCallAddAttachment:
+ - to add an mandatory argument 'attachmentfile'
+ - attachmentfile is a python file descriptor pointing to the file
+ - to expand parameter list with key/value pairs
+ 'filename', 'filetype', 'content'
+ from 'attachmentfile' before calling the server method
+@decoMakerApiCallChangePosToOptArg(argPos, argName):
+ - to change the positional argument ARGPOS into an optional argument ARGNAME.
+ - used for example in createTestPlan to support TL 1.9.13 and 1.9.14
+ argument definitions
+ - with TL 1.9.13 'testprojectname' was mandatory and expected as argPos 2
+ - with TL 1.9.14 'testproject' is optional, cause new argument 'prefix'
+ could be used as alternative
+"""
+
+
+
+# decorators for generic api calls
+# see http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python
+
+def decoApiCallWithoutArgs(methodAPI):
+ """ Decorator for calling server methods without arguments """
+
+ # register methods without positional and optional arguments
+ registerMethod(methodAPI.__name__)
+
+ @wraps(methodAPI)
+ def wrapperWithoutArgs(self):
+ return self.callServerWithPosArgs(methodAPI.__name__)
+ return wrapperWithoutArgs
+
+def decoMakerApiCallWithArgs(argNamesPositional=[], argNamesOptional=[]):
+ """ creates a decorator for calling a server method with arguments
+
+ argNamesPositional defines a list of positional arguments, which should be
+ registered in the global apiMethodsArgNames for the server method
+ argNamesOptional defines a list of optional arguments, which should be
+ registered in the global apiMethodsArgNames for the server method
+
+ """
+
+ def decoApiCallWithArgs(methodAPI):
+ """ Decorator for calling a server method with arguments """
+
+ # register methods positional and optional arguments
+ registerMethod(methodAPI.__name__, argNamesPositional, argNamesOptional)
+ # define the method server call
+ @wraps(methodAPI)
+ def wrapperWithArgs(self, *argsPositional, **argsOptional):
+ return self.callServerWithPosArgs(methodAPI.__name__,
+ *argsPositional, **argsOptional)
+ return wrapperWithArgs
+ return decoApiCallWithArgs
+
+def decoApiCallAddDevKey(methodAPI):
+ """ Decorator to expand parameter list with devKey"""
+ # register additional optional argument devKey
+ registerArgOptional(methodAPI.__name__, 'devKey')
+ @wraps(methodAPI)
+ def wrapperAddDevKey(self, *argsPositional, **argsOptional):
+ if not ('devKey' in argsOptional):
+ argsOptional['devKey'] = self.devKey
+ return methodAPI(self, *argsPositional, **argsOptional)
+ return wrapperAddDevKey
+
+def decoMakerApiCallReplaceTLResponseError(replaceCode=None, replaceValue=[]):
+ """ creates a decorator, which replace an TLResponseError with a new value
+
+ Default (replaceCode=None) handles the cause 'Empty Result'
+ - ok for getProjectTestPlans, getBuildsForTestPlan, which returns just ''
+ Problems are getTestPlanByName, getFirstLevelTestSuitesForTestProject
+ - they do not return just '', they returns the error message
+ 3041: Test plan (noPlatform) has no platforms linked
+ 7008: Test Project (noSuite) is empty
+ could be handled with replaceCode=3041 / replaceCode=7008
+
+ Default (replaceValue=[]) new return value is an empty list
+ - could be changed to other things like {}
+
+ """
+ # for understanding, what we are doing here please read
+ # # see http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python
+ # - Passing arguments to the decorator
+
+ def decoApiCallReplaceTLResponseError(methodAPI):
+ """ Decorator to replace an TLResponseError with an empty list """
+ @wraps(methodAPI)
+ def wrapperReplaceTLResponseError(self, *argsPositional, **argsOptional):
+ response = None
+ try:
+ response = methodAPI(self, *argsPositional, **argsOptional)
+ except TLResponseError as tl_err:
+ if tl_err.code == replaceCode:
+ # empty result (response == '') -> code == None
+ # special case NoPlatform -> code == 3041
+ response = replaceValue
+ else:
+ # seems to be another response failure - we forward it
+ raise
+ return response
+ return wrapperReplaceTLResponseError
+ return decoApiCallReplaceTLResponseError
+
+def decoApiCallAddAttachment(methodAPI):
+ """ Decorator to expand parameter list with devKey and attachmentfile
+ attachmentfile is a python file descriptor pointing to the file
+ """
+ registerArgOptional(methodAPI.__name__, 'devKey')
+ registerArgNonApi(methodAPI.__name__, 'attachmentfile')
+ @wraps(methodAPI)
+ def wrapperAddAttachment(self, attachmentfile, *argsPositional, **argsOptional):
+ if not ('devKey' in argsOptional):
+ argsOptional['devKey'] = self.devKey
+ argsAttachment = self._getAttachmentArgs(attachmentfile)
+ # add additional key/value pairs from argsOptional
+ # although overwrites filename, filetype, content with user definition
+ # if they exist
+ argsAttachment.update(argsOptional)
+ return methodAPI(self, *argsPositional, **argsAttachment)
+ return wrapperAddAttachment
+
+def decoMakerApiCallChangePosToOptArg(argPos, argName):
+ """ creates a decorator, which change the positional argument ARGPOS into
+ an optional argument ARGNAME.
+
+ argPos=1 is the first positional arg
+ """
+ # for understanding, what we are doing here please read
+ # # see http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python
+ # - Passing arguments to the decorator
+
+ def decoApiCallChangePosToOptArg(methodAPI):
+ """ Decorator to change a positional argument into an optional ARGNAME """
+ @wraps(methodAPI)
+ def wrapperChangePosToOptArg(self, *argsPositional, **argsOptional):
+ argsPositionalChanged = list(argsPositional)
+ if (argPos > 0) and (len(argsPositional) >= argPos):
+ argValue = argsPositionalChanged.pop(argPos - 1)
+ argsOptional[argName] = argValue
+ return methodAPI(self, *argsPositionalChanged, **argsOptional)
+ return wrapperChangePosToOptArg
+ return decoApiCallChangePosToOptArg
diff --git a/src/testlink/testlinkerrors.py b/src/testlink/testlinkerrors.py
index ae75d23..1795d8b 100644
--- a/src/testlink/testlinkerrors.py
+++ b/src/testlink/testlinkerrors.py
@@ -1,7 +1,7 @@
#! /usr/bin/python
# -*- coding: UTF-8 -*-
-# Copyright 2012 Patrick Dassier, Luiko Czub, TestLink-API-Python-client developers
+# Copyright 2012-2019 Patrick Dassier, Luiko Czub, TestLink-API-Python-client developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -34,5 +34,25 @@ class TLConnectionError(TestLinkError):
class TLAPIError(TestLinkError):
""" API error
- wrong method name ? - misssing required args? """
+
+class TLArgError(TestLinkError):
+ """ Call error
+ - wrong number of mandatory params ? - wrong param type? """
+
+class TLResponseError(TestLinkError):
+ """ Response error
+ - Response is empty or includes error codes """
+
+ def __init__(self, methodNameAPI, argsOptional, message, code=None):
+ self.methodNameAPI = methodNameAPI
+ self.argsOptional = argsOptional
+ self.message = message
+ self.code = code
+ msg = '%s\n%s(%s)' % (message, methodNameAPI, argsOptional)
+ if code:
+ msg = '%s: %s' % (code, msg)
+ return super(TLResponseError, self).__init__(msg)
+
+
\ No newline at end of file
diff --git a/src/testlink/testlinkhelper.py b/src/testlink/testlinkhelper.py
index 1601b03..fb866cc 100644
--- a/src/testlink/testlinkhelper.py
+++ b/src/testlink/testlinkhelper.py
@@ -1,7 +1,7 @@
#! /usr/bin/python
# -*- coding: UTF-8 -*-
-# Copyright 2012 Luiko Czub, TestLink-API-Python-client developers
+# Copyright 2012-2020 Luiko Czub, TestLink-API-Python-client developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,14 +17,20 @@
#
# ------------------------------------------------------------------------
-import os
+import os, sys
from argparse import ArgumentParser
-from version import VERSION
+from .version import VERSION
+import ssl
+IS_PY3 = sys.version_info[0] > 2
+if IS_PY3:
+ from .proxiedtransport3 import ProxiedTransport
+else:
+ from .proxiedtransport2 import ProxiedTransport
class TestLinkHelper(object):
""" Helper Class to find out the TestLink connection parameters.
- a) TestLink Server URL of XMLRPC
+ a) TestLink Server URL of XML-RPC
environment variable - TESTLINK_API_PYTHON_SERVER_URL
default value - http://localhost/testlink/lib/api/xmlrpc.php
command line arg - server_url
@@ -32,6 +38,10 @@ class TestLinkHelper(object):
environment variable - TESTLINK_API_PYTHON_DEVKEY
default value - 42
command line arg - devKey
+ c) Users devKey generated by TestLink
+ environment variable - http_proxy
+ default value - not set ('')
+ command line arg - proxy
Examples 1 - init TestlinkAPIClient with environment variables
- define connection parameters in environment variables
@@ -47,19 +57,24 @@ class TestLinkHelper(object):
tl_helper.connect(TestLink)
-> returns a TestLink instance
+ Attention: TL 197 changed the URL of XML-RPC
+ from http://localhost/testlink/lib/api/xmlrpc.php
+ to http://localhost/testlink/lib/api/xmlrpc/v1/xmlrpc.php
"""
- __slots__ = ['_server_url', '_devkey']
+ __slots__ = ['_server_url', '_devkey', '_proxy']
ENVNAME_SERVER_URL = 'TESTLINK_API_PYTHON_SERVER_URL'
ENVNAME_DEVKEY = 'TESTLINK_API_PYTHON_DEVKEY'
+ ENVNAME_PROXY = 'http_proxy'
DEFAULT_SERVER_URL = 'http://localhost/testlink/lib/api/xmlrpc.php'
DEFAULT_DEVKEY = '42'
- DEFAULT_DESCRIPTION = 'Python XMLRPC client for the TestLink API v%s' \
+ DEFAULT_PROXY = ''
+ DEFAULT_DESCRIPTION = 'Python XML-RPC client for the TestLink API v%s' \
% VERSION
- def __init__(self, server_url=None, devkey=None):
- """ fill slots _server_url and _devkey
+ def __init__(self, server_url=None, devkey=None, proxy=None):
+ """ fill slots _server_url, _devkey and _proxy
Priority:
1. init args
2. environment variables
@@ -67,28 +82,38 @@ def __init__(self, server_url=None, devkey=None):
"""
self._server_url = server_url
self._devkey = devkey
+ self._proxy = proxy
+ self._setParams()
+
+ def _setParams(self):
self._setParamsFromEnv()
-
+
def _setParamsFromEnv(self):
- """ fill empty slots _server_url and _devkey from environment variables
+ """ fill empty slots from environment variables
_server_url <- TESTLINK_API_PYTHON_SERVER_URL
_devkey <- TESTLINK_API_PYTHON_DEVKEY
+ _proxy <- http_proxy
If environment variables are not defined, defaults values are set.
"""
- if self._server_url == None:
+ if self._server_url is None:
self._server_url = os.getenv(self.ENVNAME_SERVER_URL,
self.DEFAULT_SERVER_URL)
- if self._devkey == None:
+ if self._devkey is None:
self._devkey = os.getenv(self.ENVNAME_DEVKEY, self.DEFAULT_DEVKEY)
+ if self._proxy is None:
+ self._proxy = os.getenv(self.ENVNAME_PROXY, self.DEFAULT_PROXY)
+
def _createArgparser(self, usage):
""" returns a parser for command line arguments """
a_parser = ArgumentParser( description=usage)
# optional command line parameters
a_parser.add_argument('--server_url', default=self._server_url,
- help='TestLink Server URL of XMLRPC (default: %(default)s) ')
+ help='TestLink Server URL of XML-RPC (default: %(default)s) ')
+ a_parser.add_argument('--proxy', default=self._proxy,
+ help='HTTP Proxy (default: %(default)s) ')
# pseudo optional command line parameters,
# must be set individual for each user
a_parser.add_argument('--devKey', default=self._devkey,
@@ -96,19 +121,36 @@ def _createArgparser(self, usage):
return a_parser
def setParamsFromArgs(self, usage=DEFAULT_DESCRIPTION, args=None):
- """ fill slots _server_url and _devkey from command line arguments
+ """ fill slots from command line arguments
_server_url <- --server_url
_devkey <- --devKey
-
+ _proxy <- --proxy
+
uses current values of these slots as default values
"""
a_parser = self._createArgparser(usage)
args = a_parser.parse_args(args)
self._server_url = args.server_url
self._devkey = args.devKey
+ self._proxy = args.proxy
-
- def connect(self, tl_api_class):
+ def _getProxiedTransport(self):
+ """ creates and return a ProxiedTransport with self._proxy settings """
+
+ a_pt = ProxiedTransport()
+ a_pt.set_proxy(self._proxy)
+ return a_pt
+
+ def connect(self, tl_api_class, **kwargs):
""" returns a new instance of TL_API_CLASS """
- return tl_api_class(self._server_url, self._devkey)
-
\ No newline at end of file
+
+ if self._proxy:
+ kwargs['transport'] = self._getProxiedTransport()
+
+ # must we add an uncertified context for a https connection?
+ # only, if kwargs not includes a context
+ if self._server_url.lower().startswith('https'):
+ if not ('context' in kwargs):
+ kwargs['context'] = ssl._create_unverified_context()
+
+ return tl_api_class(self._server_url, self._devkey, **kwargs)
diff --git a/src/testlink/testreporter.py b/src/testlink/testreporter.py
new file mode 100644
index 0000000..fed5ee5
--- /dev/null
+++ b/src/testlink/testreporter.py
@@ -0,0 +1,253 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2017-2019 Brian-Willams, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+
+from testlink.testlinkerrors import TLResponseError, TLArgError
+
+
+class TestReporter(dict):
+ def __init__(self, tls, testcases, *args, **kwargs):
+ """This can be given one or more testcases, but they all must have the
+ same project, plan, and platform.
+
+ TESTCASES must be one or a list of full external testcase id
+ TLS must be an instance of TestlinkAPIClient, defining a
+ XMLRPC connection to a TestLink Server"""
+ super(TestReporter, self).__init__(*args, **kwargs)
+ self.tls = tls
+ # handle single testcase
+ self.testcases = testcases if isinstance(testcases, list) else [testcases]
+ self._plan_testcases = None
+ self.remove_non_report_kwargs()
+ self._platformname_generated = False
+
+ def remove_non_report_kwargs(self):
+ self.buildname = self.pop('buildname')
+
+ default_note = "created automatically with {}".format(self.__class__.__name__)
+ self.buildnotes = self.pop('buildnotes', default_note)
+ self.testplannotes = self.pop('testplannotes', default_note)
+ self.platformnotes = self.pop('platformnotes', default_note)
+
+ def setup_testlink(self):
+ """Call properties that may set report kwarg values."""
+ self.testprojectname
+ self.testprojectid
+ self.testplanid
+ self.testplanname
+ self.platformname
+ self.platformid
+ self.buildid
+
+ def _get_project_name_by_id(self):
+ if self.testprojectid:
+ for project in self.tls.getProjects():
+ if project['id'] == self.testprojectid:
+ return project['name']
+
+ def _projectname_getter(self):
+ if not self.get('testprojectname') and self.testprojectid:
+ self['testprojectname'] = self._get_project_name_by_id()
+ return self.get('testprojectname')
+
+ @property
+ def testprojectname(self):
+ return self._projectname_getter()
+
+ def _get_project_id(self):
+ tpid = self.get('testprojectid')
+ if not tpid and self.testprojectname:
+ self['testprojectid'] = self.tls.getProjectIDByName(self['testprojectname'])
+ return self['testprojectid']
+ return tpid
+
+ def _get_project_id_or_none(self):
+ project_id = self._get_project_id()
+ # If not found the id will return as -1
+ if project_id == -1:
+ project_id = None
+ return project_id
+
+ @property
+ def testprojectid(self):
+ self['testprojectid'] = self._get_project_id_or_none()
+ return self.get('testprojectid')
+
+ @property
+ def testplanid(self):
+ return self.get('testplanid')
+
+ @property
+ def testplanname(self):
+ return self.get('testplanname')
+
+ @property
+ def platformname(self):
+ """Return a platformname added to the testplan if there is one."""
+ return self.get('platformname')
+
+ @property
+ def platformid(self):
+ return self.get('platformid')
+
+ @property
+ def buildid(self):
+ return self.get('buildid')
+
+ @property
+ def plan_tcids(self):
+ if not self._plan_testcases:
+ self._plan_testcases = set()
+ tc_dict = self.tls.getTestCasesForTestPlan(self.testplanid)
+ try:
+ for _, platform in tc_dict.items():
+ for k, v in platform.items():
+ self._plan_testcases.add(v['full_external_id'])
+ except AttributeError:
+ # getTestCasesForTestPlan returns an empty list instead of an empty dict
+ pass
+ return self._plan_testcases
+
+ def reportgen(self):
+ """For use if you need to look at the status returns of individual reporting."""
+ self.setup_testlink()
+ for testcase in self.testcases:
+ yield self.tls.reportTCResult(testcaseexternalid=testcase, **self)
+
+ def report(self):
+ for _ in self.reportgen():
+ pass
+
+
+class AddTestCaseReporter(TestReporter):
+ """Add testcase to testplan if not added."""
+ def setup_testlink(self):
+ super(AddTestCaseReporter, self).setup_testlink()
+ self.ensure_testcases_in_plan()
+
+ def ensure_testcases_in_plan(self):
+ # Get the platformid if possible or else addition will fail
+ self.platformid
+ for testcase in self.testcases:
+ # Can't check if testcase is in plan_tcids, because that won't work if it's there, but of the wrong platform
+ try:
+ self.tls.addTestCaseToTestPlan(
+ self.testprojectid, self.testplanid, testcase, self.get_latest_tc_version(testcase),
+ platformid=self.platformid
+ )
+ except TLResponseError as e:
+ # Test Case version is already linked to Test Plan
+ if e.code == 3045:
+ pass
+ else:
+ raise
+
+ def get_latest_tc_version(self, testcaseexternalid):
+ return int(self.tls.getTestCase(None, testcaseexternalid=testcaseexternalid)[0]['version'])
+
+
+class AddTestPlanReporter(TestReporter):
+ @property
+ def testplanid(self):
+ if not self.get('testplanid'):
+ try:
+ self['testplanid'] = self.tls.getTestPlanByName(self.testprojectname, self.testplanname)[0]['id']
+ except TLResponseError as e:
+ # Name does not exist
+ if e.code == 3033:
+ self['testplanid'] = self._generate_testplanid()
+ else:
+ raise
+ except TypeError:
+ self['testplanid'] = self._generate_testplanid()
+ return self['testplanid']
+
+ def _generate_testplanid(self):
+ """This won't necessarily be able to create a testplanid. It requires a planname and projectname."""
+ if 'testplanname' not in self:
+ raise TLArgError("Need testplanname to generate a testplan for results.")
+
+ tp = self.tls.createTestPlan(self['testplanname'], self.testprojectname,
+ notes=self.testplannotes)
+ self['testplanid'] = tp[0]['id']
+ return self['testplanid']
+
+
+class AddPlatformReporter(TestReporter):
+ @property
+ def platformname(self):
+ """Return a platformname added to the testplan if there is one."""
+ pn_kwarg = self.get('platformname')
+ if pn_kwarg and self._platformname_generated is False:
+ # If we try to create platform and catch platform already exists error (12000) it sometimes duplicates a
+ # platformname
+ try:
+ self.tls.addPlatformToTestPlan(self.testplanid, pn_kwarg)
+ except TLResponseError as e:
+ if int(e.code) == 235:
+ self.tls.createPlatform(self.testprojectname, pn_kwarg,
+ notes=self.platformnotes,
+ platformondesign=True, platformonexecution=True)
+ self.tls.addPlatformToTestPlan(self.testplanid, pn_kwarg)
+ self._platformname_generated = True
+ else:
+ raise
+ return pn_kwarg
+
+ @property
+ def platformid(self):
+ if not self.get('platformid'):
+ self['platformid'] = self.getPlatformID(self.platformname)
+ # This action is idempotent
+ self.tls.addPlatformToTestPlan(self.testplanid, self.platformname)
+ return self['platformid']
+
+ def getPlatformID(self, platformname, _firstrun=True):
+ """
+ This is hardcoded for platformname to always be self.platformname
+ """
+ platforms = self.tls.getTestPlanPlatforms(self.testplanid)
+ for platform in platforms:
+ # https://github.com/Brian-Williams/TestLink-API-Python-client/issues/1
+ if platform['name'].lower() == platformname.lower():
+ return platform['id']
+ # Platformname houses platform creation as platform creation w/o a name isn't possible
+ if not self.platformname:
+ raise TLArgError(
+ "Couldn't find platformid for {}.{}, "
+ "please provide a platformname to generate.".format(self.testplanid, self.platformname)
+ )
+ if _firstrun is True:
+ return self.getPlatformID(self.platformname, _firstrun=False)
+ else:
+ raise TLArgError("PlatformID not found after generated from platformname '{}' "
+ "in test plan {}.".format(self.platformname, self.testplanid))
+
+
+class AddBuildReporter(TestReporter):
+ @property
+ def buildid(self):
+ bid = self.get('buildid')
+ if not bid or bid not in self.tls.getBuildsForTestPlan(self.testplanid):
+ self['buildid'] = self._generate_buildid()
+ return self.get('buildid')
+
+ def _generate_buildid(self):
+ r = self.tls.createBuild(self.testplanid, self.buildname, self.buildnotes)
+ return r[0]['id']
diff --git a/src/testlink/version.py b/src/testlink/version.py
index 6ceccdb..4656c6d 100644
--- a/src/testlink/version.py
+++ b/src/testlink/version.py
@@ -1,7 +1,7 @@
#! /usr/bin/python
# -*- coding: UTF-8 -*-
-# Copyright 2012 TestLink-API-Python-client developers
+# Copyright 2013-2021 TestLink-API-Python-client developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,4 +17,5 @@
#
# ------------------------------------------------------------------------
-VERSION = '0.4.0-RC1'
\ No newline at end of file
+VERSION = '0.8.2-dev141'
+TL_RELEASE = '1.9.20-fixed_c88e348ce'
diff --git a/test/conftest.py b/test/conftest.py
new file mode 100644
index 0000000..0de5044
--- /dev/null
+++ b/test/conftest.py
@@ -0,0 +1,76 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2018-2019 Luiko Czub, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+import os.path
+import pytest
+from testlink import TestlinkAPIClient, TestlinkAPIGeneric, TestLinkHelper
+
+# example text file attachment = this python file
+# why not using os.path.realpath(__file__)
+# -> cause __file__ could be compiled python file *.pyc, if the test run is
+# repeated without changing the test code
+ATTACHMENT_EXAMPLE_TEXT= os.path.join(os.path.dirname(__file__),
+ os.path.basename(__file__))
+
+#attachemantFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r')
+
+@pytest.fixture()
+def attachmentFile():
+ ''' open readonly attachment sample before test and close it afterwards '''
+ aFile = open(ATTACHMENT_EXAMPLE_TEXT, 'r')
+ yield aFile
+ aFile.close()
+
+@pytest.fixture(scope='session')
+def api_helper_class():
+ return TestLinkHelper
+
+
+@pytest.fixture(scope='session')
+def api_generic_client(api_helper_class):
+ ''' Init TestlinkAPIGeneric Client with connection parameters defined in
+ environment variables
+ TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY
+ '''
+ return api_helper_class().connect(TestlinkAPIGeneric)
+
+@pytest.fixture(scope='session')
+def api_general_client(api_helper_class):
+ ''' Init TestlinkAPIClient Client with connection parameters defined in
+ environment variables
+ TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY
+ '''
+ return api_helper_class().connect(TestlinkAPIClient)
+
+@pytest.fixture(scope='session', params=[TestlinkAPIGeneric, TestlinkAPIClient])
+def api_client_class(request):
+ ''' all variations of Testlink API Client classes '''
+ return request.param
+
+@pytest.fixture(scope='session')
+def api_client(api_client_class, api_helper_class):
+ ''' Init Testlink API Client class defined in fixtures api_client_class with
+ connection parameters defined in environment variables
+ TESTLINK_API_PYTHON_DEVKEY and TESTLINK_API_PYTHON_DEVKEY
+
+ Tests will be call for each Testlink API Client class, defined in
+ fixtures parameter list
+ '''
+ return api_helper_class().connect(api_client_class)
+
diff --git a/test/test.py b/test/test.py
deleted file mode 100644
index 9984b75..0000000
--- a/test/test.py
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Copyright 2011-2012 pade (Patrick Dassier), TestLink-API-Python-client developers
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-# ------------------------------------------------------------------------
-
-'''
- Source file
- File name: test.py
- Creation date: 04-10-2012
- Author: dassier
-
-'''
-
-'''
-Fichier de test pour le module "TestLinkAPI.py"
-'''
-
-import re
-from testlink import TestLink, TestLinkError, TestLinkHelper
-from nose.tools import *
-
-class TestClass():
- def setUp(self):
- """Initialisation
- """
-
- # precondition - SERVEUR_URL and KEY are defined in environment
- # TESTLINK_API_PYTHON_SERVER_URL=http://localhost/testlink/lib/api/xmlrpc.php
- # TESTLINK_API_PYTHON_DEVKEY=7ec252ab966ce88fd92c25d08635672b
- self.client = TestLinkHelper().connect(TestLink)
-
- def test_getTestCaseIDByName(self):
- """ getTestCaseIDByName test
- """
- val = self.client.getTestCaseIDByName("Fin de programme", "Séquence 2", "Test 2")
- # 31 is test case id
- assert_equal(val, '31' )
-
- # Check if an error is raised in case of bad parameters
- assert_raises(TestLinkError, self.client.getTestCaseIDByName, "Initialisation", "Séquence 1", "Test 2")
-
- def test_getTestProjectByName(self):
- project = self.client.getTestProjectByName("Test 2")
- assert_equals(type(project), dict)
- # Check if an error is raised in case of bad parameters
- assert_raises(TestLinkError, self.client.getTestProjectByName, "Unknown project")
-
- def test_getTestPlanByName(self):
- plan_ok = self.client.getTestPlanByName("Test 2", "Full")
-
- # Assume that plan id is 33
- assert_equal(plan_ok['id'], '33')
-
- assert_raises(TestLinkError, self.client.getTestPlanByName, "Test 2", "Name Error")
-
- def test_getBuildByName(self):
- pass
-
- def test_reportResult(self):
- dico = {'testProjectName': 'Automatique',
- 'testPlanName': 'FullAuto',
- 'buildName': 'V0.1'}
- execid = self.client.reportResult("p", "test1", "S1", "An example of note", **dico)
- assert_equal(type(execid), str)
-
- execid = self.client.reportResult("f", "test2", "S1", **dico)
- assert_equal(type(execid), str)
-
diff --git a/test/utest-offline/test_apiClients_whatArgs.py b/test/utest-offline/test_apiClients_whatArgs.py
new file mode 100644
index 0000000..d6fd686
--- /dev/null
+++ b/test/utest-offline/test_apiClients_whatArgs.py
@@ -0,0 +1,152 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2018-2021 Luiko Czub, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+# TestCases for Testlink API clients whatArgs calls
+# - TestlinkAPIClient, TestlinkAPIGeneric
+#
+
+import pytest
+import re
+
+def test_whatArgs_noArgs(api_client):
+ response = api_client.whatArgs('sayHello')
+ assert re.match('sayHello().*', response)
+
+def test_whatArgs_onlyOptionalArgs(api_client):
+ response = api_client.whatArgs('getTestCaseKeywords')
+ assert re.match(r'getTestCaseKeywords\(\[.*=<.*>\].*\).*',
+ response)
+
+def test_whatArgs_OptionalAndPositionalArgs(api_client):
+ response = api_client.whatArgs('createBuild')
+ assert re.match(r'createBuild\(<.*>.*\).*', response)
+
+def test_whatArgs_MandatoryArgs(api_client):
+ response = api_client.whatArgs('uploadExecutionAttachment')
+ assert re.match(r'uploadExecutionAttachment\(, <.*>.*\).*',
+ response)
+
+def test_whatArgs_unknownMethods(api_client):
+ response = api_client.whatArgs('apiUnknown')
+ assert re.match(r"callServerWithPosArgs\('apiUnknown', \[apiArg=\]\)",
+ response)
+
+test_data_apiCall_descriptions_equal_all = [
+ ('getTestCasesForTestSuite', ['getkeywords=']),
+ ('reportTCResult', ['user=', 'execduration=',
+ 'timestamp=', 'steps=',
+ "[{'step_number' : 6,"]),
+ ('getLastExecutionResult', ['options=','getBugs']),
+ ('getTestCasesForTestPlan', [',',
+ 'buildid=', 'platformid=',
+ 'testcaseid=', 'keywordid=',
+ 'keywords=', 'executed=',
+ 'assignedto=', 'executestatus=',
+ 'executiontype=', 'getstepinfo=',
+ 'details=', 'customfields=',
+ 'keywordid - keywords']),
+ ('createTestCase', [',', ',', ',',
+ ',', ',', #',',
+ 'preconditions=',
+ 'importance=',
+ 'executiontype=', 'order=',
+ 'internalid=',
+ 'checkduplicatedname=',
+ 'actiononduplicatedname=',
+ 'status=',
+ 'estimatedexecduration=']),
+ ('createTestPlan', ['prefix=', 'testprojectname=']),
+ ('getTestSuite',['', '']),
+ ('updateTestSuite',[',', 'testprojectid=',
+ 'prefix=', 'parentid=',
+ 'testsuitename=', 'details=',
+ 'order=']),
+ ('createBuild',[',', ',', 'active=',
+ 'copytestersfrombuild=']),
+ ('addTestCaseToTestPlan',[',', ',',
+ ',', ',',
+ 'platformid=',
+ 'executionorder=',
+ 'urgency=', 'overwrite=']),
+ ('createTestProject',[',', ',',
+ 'notes=', 'active=',
+ 'public=', 'options=',
+ 'itsname=', 'itsenabled=']),
+ ('getIssueTrackerSystem',[',']),
+ ('getExecutionSet',[',', 'testcaseid=',
+ 'testcaseexternalid=',
+ 'buildid=', 'buildname=',
+ 'platformid=',
+ 'platformname=', 'options=']),
+ ('getRequirements',[',', 'testplanid=',
+ 'platformid=']),
+ ('getReqCoverage',[',', ',']),
+ ('setTestCaseTestSuite',[',', ',']),
+ ('getTestSuiteAttachments',[',']),
+ ('getAllExecutionsResults',[',','testcaseid=',
+ 'testcaseexternalid=',
+ 'platformid=', 'buildid=',
+ 'options=']),
+ ('getTestCaseAttachments',['version=',
+ 'testcaseexternalid=']),
+ ('uploadTestCaseAttachment',[',', ',',
+ 'title=', 'description=',
+ 'filename=', 'filetype=',
+ 'content=']),
+ ('createPlatform',[',', ',', 'notes=',
+ 'platformondesign=',
+ 'platformonexecution=']),
+ ('closeBuild', ['']),
+ ('createUser', ['', '', '', '',
+ 'password=']),
+ ('setUserRoleOnProject', ['', '', ''])
+ ]
+
+@pytest.mark.parametrize("apiCall, descriptions",
+ test_data_apiCall_descriptions_equal_all)
+def test_whatArgs_apiCall_descriptions_equal_all(api_client, apiCall, descriptions):
+ argsDescription = api_client.whatArgs(apiCall)
+ for parts in descriptions:
+ assert parts in argsDescription
+
+test_data_apiCall_descriptions_only_generic = [
+ ('createTestCase', [',']),
+ ('createBuild',['buildnotes=']),
+ ('getTestCaseAttachments',['testcaseid='])
+ ]
+@pytest.mark.parametrize("apiCall, descriptions",
+ test_data_apiCall_descriptions_only_generic)
+def test_whatArgs_apiCall_descriptions_only_generic(api_generic_client, apiCall, descriptions):
+ argsDescription = api_generic_client.whatArgs(apiCall)
+ for parts in descriptions:
+ assert parts in argsDescription
+
+test_data_apiCall_descriptions_only_general = [
+ ('createTestCase', ['steps=']),
+ ('createBuild',[',']),
+ ('getTestCaseAttachments',[','])
+ ]
+@pytest.mark.parametrize("apiCall, descriptions",
+ test_data_apiCall_descriptions_only_general)
+def test_whatArgs_apiCall_descriptions_only_general(api_general_client, apiCall, descriptions):
+ argsDescription = api_general_client.whatArgs(apiCall)
+ for parts in descriptions:
+ assert parts in argsDescription
+
+
\ No newline at end of file
diff --git a/test/utest-offline/test_py3_vs_py2.py b/test/utest-offline/test_py3_vs_py2.py
new file mode 100644
index 0000000..aa6c84e
--- /dev/null
+++ b/test/utest-offline/test_py3_vs_py2.py
@@ -0,0 +1,31 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2018-2020 Luiko Czub, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+# TestCases for Testlink API clients handling py2 and py3 differences
+# - TestlinkAPIClient, TestlinkAPIGeneric
+#
+
+import pytest
+#from conftest import api_general_client, api_generic_client
+
+def test_IS_PY3_same_state():
+ from testlink.testlinkhelper import IS_PY3 as proxie_is_py3
+ from testlink.testlinkapigeneric import IS_PY3 as tl_is_py3
+ assert proxie_is_py3 == tl_is_py3
+
diff --git a/test/utest-offline/testlinkapi_offline_test.py b/test/utest-offline/testlinkapi_offline_test.py
new file mode 100644
index 0000000..0e09db3
--- /dev/null
+++ b/test/utest-offline/testlinkapi_offline_test.py
@@ -0,0 +1,655 @@
+#! /usr/bin/python
+# -*- coding: UTF-8 -*-
+
+# Copyright 2012-2019 Luiko Czub, TestLink-API-Python-client developers
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# ------------------------------------------------------------------------
+
+# this test works WITHOUT an online TestLink Server
+# no calls are send to a TestLink Server
+
+import sys
+import unittest
+
+if sys.version_info[0] == 2 and sys.version_info[1] == 7:
+ # py27 and py31 assertRaisesRegexp was renamed in py32 to assertRaisesRegex
+ unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp
+
+from testlink import TestlinkAPIClient, TestLinkHelper
+from testlink.testlinkerrors import TLArgError
+
+# scenario_a includes response from a testlink 1.9.3 server
+SCENARIO_A = {'getProjects' : [
+ {'opt': {'requirementsEnabled': 0, 'testPriorityEnabled': 1,
+ 'automationEnabled': 1, 'inventoryEnabled': 0},
+ 'prefix': 'NPROAPI', 'name': 'NEW_PROJECT_API', 'color': '',
+ 'notes': 'This is a Project created with the API',
+ 'option_priority': '0',
+ 'options': 'O:8:"stdClass":4:{s:19:"requirementsEnabled";i:0;s:19:"testPriorityEnabled";i:1;s:17:"automationEnabled";i:1;s:16:"inventoryEnabled";i:0;}',
+ 'tc_counter': '2', 'option_reqs': '0', 'active': '1',
+ 'is_public': '1', 'id': '21', 'option_automation': '0'},
+ {'opt': {'requirementsEnabled': 1, 'testPriorityEnabled': 1,
+ 'automationEnabled': 1, 'inventoryEnabled': 1},
+ 'prefix': 'TP', 'name': 'TestProject', 'color': '',
+ 'notes': '
Initiales TestProject, um TestLink kennen zu lernen