TestTrack Pro is the Issue Management software from Seapine Software
TestTrack is a registered trademark of Seapine Software. http://www.seapine.com/testtrack.html
This library uses the suds library to talk to the TestTrack SDK SOAP API and includes some helpful extensions for managing your client code and interactions. There are a number of issues (and crashes) when talking to the latest TestTrack SOAP API using suds, and this library addresses those.
While this module is named testtrackpro it will work with TestTrack RM (Requirement Management) and TestTrack TCM (Test Case Management).
The TestTrack SOAP API uses a client cookie for managing login sessions. This cookie must be supplied on (almost) every API call. This library provides a client wrapper object which will manage the session cookie, and even releasing the cookie (logging off) as part of a context exit.
The TestTrack SOAP API includes entity edit locking where a write lock is implicit in every edit API call. The client must release the lock with either a save or cancelSave API call. The lock will remain for 15 minuites, making other attepts to edit fail on the entity.
Python contexts allow for dealing with safely releasing locks on success or error. All objects returned form API calls which start with the string ‘edit’ will return a context object that can be used with the ‘with’ statement. At the end of the statement block the appropriate ‘save’ API call will be made. If an exception occurs in the block, the appropriate ‘cancelSave’ API call will be made. In either situation the lock will be released.
import testtrackpro
with testtrackpro.TTP('http://hostname/', 'Project', 'username', 'password') as ttp:
with ttp.editDefect(11, bDownloadAttachments=False) as defect:
defect.priority = "Immediate"
## ttp.saveDefect(defect) is called, or ttp.cancelSave(defect.recordid) on exception.
## ttp.DatabaseLogoff() is called, even if an exception occured.
Additionally there is a new special edit context API extension when using python contexts for ignoring the edit lock error, when someone else has the edit lock on an entity. This is very useful when you do not want your script or service to error out on a failed edit lock, but instead want to continue processing.
import testtrackpro
with testtrackpro.TTP('http://hostname/', 'Project', 'username', 'password') as ttp:
with ttp.editDefect(11, bDownloadAttachments=False, ignoreEditLockError=True) as defect:
defect.priority = "Immediate"
## ttp.saveDefect(defect) is called, or ttp.cancelSave(defect.recordid) on exception.
assert not testtrackpro.have_edit_lock(defect)
if testtrackpro.was_saved(defect):
# The priority was changed
pass
elif testtrackpro.has_errored(defect):
# It was not saved due to an error
pass
if testtrackpro.edit_lock_failed(defect):
# because the edit lock failed
pass
else:
# because of some other error
# NOTE: unless there was other code to catch and ignore the
# error, this code is unreachable.
pass
## ttp.DatabaseLogoff() is called, even if an exception occured.
TestTrack Python Interface
TestTrack Pro is the Issue Management software from Seapine Software.
TestTrack is a registered trademark of Seapine Software.
This library uses the suds library to talk to the TestTrack SDK SOAP API and includes some helpful extensions for managing your client code and interactions.
Seapine documentation provides a number of python samples for interacting with the TestTrack SOAP API, but there are a number of problems with their TestTrack Python Tutorial.
The sample tutorial has syntax and functional errors, and does not work with the latest versions of TestTrack and suds. Even with the versions of python and the suds library mentioned, the code will crash due to WSDL non-compliance issues when custom fields are used.
This module addresses these issues as well as provides a more managed interface to simplify development.
The TestTrack SDK SOAP API uses a client cookie argument on almost every call. You end up writting code where every API call starts with cookie.
# What object types can I query?
adt = server.service.getTableList(cookie)
# What field data is available for a given object type?
atc = server.service.getColumnsForTable(cookie, tablename)
# What filters are available?
af = server.service.getFilterList(cookie)
# Get defect 42
d = server.service.getDefect(cookie, 42)
# log out
server.service.DatabaseLogoff(cookie)
You can view this argument as an implicit self for this python interface to that API. That is you do not need to supply the cookie argument, as it will be managed for you in the client object.
import testtrackpro
ttp = testtrackpro.TTP('http://hostname/', 'Project', 'username', 'password')
adt = ttp.getTableList()
atc = ttp.getColumnsForTable(tablename)
defect = ttp.getDefect(42) # maps to getDefect(cookie, 42)
ttp.DatabaseLogoff()
Due to the implicit write locks (with 15 min timeout) on all edit API calls, clients normally would have to be very careful to trap all exceptions and capture any and all locked entities and un lock them as part of the exception handling. Thankfully python provides contexts via the ‘with’ statement that are designed for exactly this problem.
All suds objects returned be API calls which start with the string ‘edit’ will return suds objects which have been extended to be python contexts which can be used with the with statement:
with ttp.editDefect(42) as defect:
defect.priority = "Immediate"
At the end of the with block, a call to ttp.saveDefect(defect) will be made automatically, saving any pending edits, and releasing the lock. Explicit calls to saveDefect or cancelSaveDefect also work within the context block.
If an exception occurs, then a call to ttp.cancelSaveDefect(defect.recordid) will be made automatically to release the lock, without saving the defect.
Also the TTP instance object is also a context object, and will log the session out when used in a with statement.
with testtrackpro.TTP('http://hostname/', 'Project', 'username', 'password') as ttp:
defect = ttp.getDefect(42)
## ttp.DatabaseLogOff() implicitly called on success or error
As mentioned above, only one person can be editing an entity at a time on the TestTrack server. This means that you will often get an error trying to edit an entity, and there is no clean way of detecting if an entity is currently locked or not; due to race conditions.
This leads to some painful defensive programming for this very common occurance.
failed_to_edit = []
for id in defect_ids_to_edit:
try:
with ttp.editDefect(id) as defect:
defect.summary = "new changed title"
## bunch of other code
except TTPAPIError, e:
## the edit lock error is code 22, and it is a string.
if e.fault and e.fault.detail == "22":
failed_to_edit.append(id)
else:
## unexpected error, re-raise it.
raise e
This is much more work than it should be. and the try/except work being done here is again what contexts are supposed to simplify. So every edit API call has been extended to have a new keyword argument ignoreEditLockError that defaults to False. When set to True it will swallow the TTPAPIError code "22". The object returned will be an empty entity of the proper type. This will be a context entity, just as if the call had succeeded. This means that the edit entity status functions will work on the entity.
failed_to_edit = []
for id in defect_ids_to_edit:
with ttp.editDefect(id, ignoreEditLockError=True) as defect:
## helper to immediatly stop processing
break_context_on_edit_lock_failure(defect)
defect.summary = "new changed title"
## bunch of other code
if edit_lock_failed(defect):
failed_to_edit.append(id)
Note
Only the edit lock error "22" is captured. All other errors must be handled explictly in your code.
Warning
A new empty entity is returned when ignoreEditLockError is set to True. This means you MUST test to see if you have the real entity to work with.
>>> d = ttp.editDefect(1, ignoreEditLockError=True)
>>> print d.recordid
None
>>> edit_lock_failed(d)
True
>>> have_edit_lock(d)
False
>>>
Client for communicating with the TestTrack SOAP Service.
Parameters: |
|
---|
Log out of the SOAP API session, and release the stored client cookie.
Parameters: | ignore_exceptions (bool) – Set this to true to ignore connection and authentication based API errors. |
---|
It can be useful to ignore connection and authenticaiton based errors when logging out, especially when using the client as a context. If there was an error communicating with the client, we want to ignore further errors due to the implicit logoff at the end of the context to preserve the ogitional initial connection error.
Logon to the SOAP API and retrieve a new client cookie.
Parameters: |
|
---|
If the client is currently logged on, it will first logoff. If the username and or password are supplied they will update the client stored versions of these values. If they are not supplied, then the versions supplied on construction will be used.
Warning
The DatabaseLogon() API method has been depricated by Seapine, and should no longer be used. The ProjectLogon() API method should be used instead.
Logon to the SOAP API and retrieve a new client cookie.
Parameters: |
|
---|
The appropriate CProject entity can be retrieved using the getProjectList() method, of constructing one using the create() method.
If the client is currently logged on, it will first logoff. If the username and or password are supplied they will update the client stored versions of these values. If they are not supplied, then the versions supplied on construction will be used.
This is now the prefered way to logon to TestTrack now that the DatabaseLogon() method has been depricated.
Cancel the save of the edit locked context entity.
Parameters: | entity (CType) – edit entity returned by a editXXXX method call. |
---|
This is handy when you have a an entity but can not easilly get to it’s type programatically. This is useful when you recieve polymorphic array data back from the TestTrack SOAP API.
In ortherwords you can do this:
with ttp.editDefect(defectnum) as defect:
defect.something = "value"
if badthing:
ttp.cancelSave(defect)
Calling ttp.cancelSaveDefect(defect.recordid) is also safe and will prevent the call to saveDefect at the end of the context block.
Warning
If you are using edit contexts then calls to cancelSaveDefect with a recordid for the entity in the context which comes from a different structure will cause an error.
Example of errorful code:
def bad(recordid):
with ttp.editDefectBeRecordId(recordid) as defect:
ttp.cancelSaveDefect(recordid) ## bad
## at this point saveDefect(defect) will be called
Correct way:
def good(recordid):
with ttp.editDefectByRecordId(recordid) as defect:
ttp.cancelSave(defect)
## or
with ttp.editDefectByRecordId(recordid) as defect:
ttp.cancelSaveDefect(defect.recordid)
Factory Creation for TTPAPI structures
Parameters: | name (str) – SOAP Entity type name. |
---|
## Create a new defect
# Create the CDefect object.
defect = ttp.create("CDefect")
#suds doesn't automatically initialize the record id
defect.recordid = 0;
defect.summary = "This is a new defect"
defect.product = "My Product"
defect.priority = "Immediate"
# Add the defect to TestTrack.
lNewNum = ttp.addDefect(defect)
## Create a new project
project = ttp.create("CProject")
project.database = ttp.create("CDatabase")
project.database.name = "MyProject"
project.options = ttp.create("ArrayOfCProjectDataOption")
project.options.append(ttp.create("CProjectDataOption"))
project.options.append(ttp.create("CProjectDataOption"))
project.options.append(ttp.create("CProjectDataOption"))
project.options[0].name = "TestTrack Pro" # add TTP functionality.
project.options[1].name = "TestTrack TCM" # add TCM functionality.
project.options[2].name = "TestTrack RM" # add RM functionality.
# Add the project to TestTrack.
ttp.ProjectLogon(project, username, password)
Return a list of CProject entities which the user has access to on the server.
Parameters: |
|
---|
The username and password are only required if they were not supplied on client creation. If supplied they will NOT replace the client stored username, password, or client cookie.
Save the edit locked context entity.
Parameters: | entity (CType) – edit entity returned by a editXXXX method call. |
---|
This is handy when you have a an entity but can not easilly get to it’s type programatically. This is useful when you recieve polymorphic array data back from the TestTrack SOAP API.
In ortherwords you can do this:
with ttp.editDefect(a) as defecta, ttp.editDefect(b) as defectb:
defecta.something = "value"
ttp.save(defect) ## release the lock immediatly
defectb.other = defecta.other
## defectb is saved
Calling ttp.saveDefect(defecta) is also safe and will prevent the call to saveDefect(defecta) at the end of the context block.
Warning
If you are using edit contexts then calls to saveDefect with a CDefect for the same defect from a different API call will result in saveDefect being called again at the end of the context causing an error.
Example of errorful code:
def bad():
getdefect = ttp.getDefect(24)
with ttp.editDefectBeRecordId(getdefect.recordid) as defect:
getdefect.priority = "Immediate"
ttp.saveDefect(getdefect)
## at this point saveDefect(defect) will be called and will
## cause an error as there is no longer an edit lock on
## the defect.
Correct way:
def good(recordid):
with ttp.editDefectByRecordId(recordid) as defect:
ttp.cancelSave(defect)
## or
with ttp.editDefectByRecordId(recordid) as defect:
ttp.cancelSaveDefect(defect.recordid)
Base Exception for all API errors.
SOAP document associated with the error if there is one, else will be None.
Soap fault Envelope, if there is one, else will be None
Low level SOAP or HTTP reason for the error. Will always be a valid string, but may be empty if no reason can be determined. Will back off to exception message.
Errors communicating with the TestTrack SOAP Service.
Errors with authentication against the TestTrack SOAP Service.
Force immediate break in the with context if the edit lock failed.
with ttp.editDefect(1, ignoreEditLockError=True) as defect:
break_context_on_edit_lock_failure(defect) ## stop processing here
defect.priority = "Immediate"
if edit_lock_failed(defect):
# deal with the issue
pass
for multiple with statements:
with ttp.editDefect(1, ignoreEditLockError=True) as a:
with ttp.editDefect(2, ignoreEditLockError=True) as b, ttp.editDefect(3, ignoreEditLockError=True) as c:
## will break out to top if a failed lock
break_context_on_edit_lock_failure(a,b,c)
Helper function to check if an edit context entity errored on getting the edit lock. This is useful in conjunction with the special edit API argument ignoreEditLockError=True, which will supress the failed edit lock exception.
with ttp.editDefect(1, ignoreEditLockError=True) as defect:
defect.priority = "Immediate"
if edit_lock_failed(defect):
# deal with the issue
pass
Helper function to check if an edit context entity errored. This is useful when you capture and ignore an error, or as part of error handling.
try:
with ttp.editDefect(1) as defect:
defect.priority = "Immediate"
raise RuntimeError('some error')
except:
# This is bad code, please do not do this.
pass
if has_errored(defect):
# deal with the issue
pass
Helper function to check if an edit context entity is still open for edit.
with ttp.editDefect(1) as defect:
if defect.priority == "Immediate":
ttp.cancelSave(defect)
# ...
# more code
# ...
if have_edit_lock(defect):
defect.Summary = "Updated Summary"
assert not have_edit_lock(defect)
Is this an edit context entity (object returned by ttp.editXXX()) that can be safely used with the status functions in this module, and use in contexts.
Helper function to check if an edit context entity was saved. This is useful in conjunction with the special edit API argument ignoreEditLockError=True, which will supress the failed edit lock exception, and when the potential for a cancelSave was issued.
with ttp.editDefect(1, ignoreEditLockError=True) as defect:
if defect.priority == "Immediate":
ttp.cancelSave(defect)
defect.priority = "Immediate"
if was_saved(defect):
# The priority was changed
pass
elif has_errored(defect):
# It was not saved due to an error
if edit_lock_failed(defect):
# because the edit lock failed
pass
else:
# because of some other error
pass
Copyright (c) 2013, Doug Napoleone All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.