1+ # -*- coding: utf-8 -*-
12"""
23
34TestLinkAPI - v0.20
67@author: kereval.com
78Initialy based on the James Stock testlink-api-python-client R7.
89
9- Updated by Kereval to support testCase Reporting and File attachment to test execution
10-
10+ Update by pade to provide a user friendly library, with more robustness and error management
1111"""
1212import xmlrpclib
13+ import sys
14+ from datetime import date
15+
16+ class TestLinkErrors (Exception ):
17+ """ Basic error handler
18+ Return message pass as argument
19+ """
20+ def __init__ (self , msg ):
21+ self .__msg = msg
22+
23+ def __str__ (self ):
24+ return self .__msg
25+
26+ class TestLinkAPIClient (object ):
27+
1328
14- class TestlinkAPIClient :
15-
1629 def __init__ (self , server_url , devKey ):
1730 self .server = xmlrpclib .Server (server_url )
1831 self .devKey = devKey
@@ -139,14 +152,32 @@ def getTestCaseCustomFieldDesignValue(self, testcaseexternalid, version,
139152 'details' : str (details )}
140153 return self .server .tl .getTestCaseCustomFieldDesignValue (argsAPI )
141154
142- def getTestCaseIDByName (self , testCaseName ):
143- """ getTestCaseIDByName :
144- Find a test case by its name
145- """
155+ def getTestCaseIDByName (self , testCaseName , testSuiteName = None , testProjectName = None ):
156+ """
157+ Find a test case by its name
158+ testSuiteName and testProjectName are optionals arguments
159+ This function return a list of tests cases
160+ """
146161 argsAPI = {'devKey' : self .devKey ,
147162 'testcasename' :str (testCaseName )}
148- return self .server .tl .getTestCaseIDByName (argsAPI )
149-
163+
164+ if testSuiteName is not None :
165+ argsAPI .update ({'testsuitename' :str (testSuiteName )})
166+
167+ if testProjectName is not None :
168+ argsAPI .update ({'testprojectname' :str (testProjectName )})
169+
170+ # Server return can be a list or a dictionnary !
171+ # This function always return a list
172+ ret_srv = self .server .tl .getTestCaseIDByName (argsAPI )
173+ if type (ret_srv ) == dict :
174+ retval = []
175+ for value in ret_srv .values ():
176+ retval .append (value )
177+ return retval
178+ else :
179+ return ret_srv
180+
150181 def getTestCasesForTestPlan (self , * args ):
151182 """ getTestCasesForTestPlan :
152183 List test cases linked to a test plan
@@ -329,12 +360,12 @@ def createTestCase(self, *args):
329360 self .stepsList = []
330361 return ret
331362
332- def reportTCResult (self , testcaseid , testplanid , buildid , status , notes ):
363+ def reportTCResult (self , testcaseid , testplanid , buildname , status , notes ):
333364 """
334365 Report execution result
335366 testcaseid: internal testlink id of the test case
336367 testplanid: testplan associated with the test case
337- buildid : build version of the test case
368+ buildname : build name of the test case
338369 status: test verdict ('p': pass,'f': fail,'b': blocked)
339370
340371 Return : [{'status': True, 'operation': 'reportTCResult', 'message': 'Success!', 'overwrite': False, 'id': '37'}]
@@ -343,11 +374,13 @@ def reportTCResult(self, testcaseid, testplanid, buildid, status, notes ):
343374 argsAPI = {'devKey' : self .devKey ,
344375 'testcaseid' : testcaseid ,
345376 'testplanid' : testplanid ,
346- 'buildid' :buildid ,
347377 'status' : status ,
348- 'notes' : notes
378+ 'buildname' : buildname ,
379+ 'notes' : str (notes )
349380 }
350381 return self .server .tl .reportTCResult (argsAPI )
382+
383+
351384
352385 def uploadExecutionAttachment (self ,attachmentfile ,executionid ,title ,description ):
353386 """
@@ -486,47 +519,184 @@ def initStep(self, actions, expected_results, execution_type):
486519 Initializes the list which stores the Steps of a Test Case to create
487520 """
488521 self .stepsList = []
489- list = {}
490- list ['step_number' ] = '1'
491- list ['actions' ] = actions
492- list ['expected_results' ] = expected_results
493- list ['execution_type' ] = str (execution_type )
494- self .stepsList .append (list )
522+ lst = {}
523+ lst ['step_number' ] = '1'
524+ lst ['actions' ] = actions
525+ lst ['expected_results' ] = expected_results
526+ lst ['execution_type' ] = str (execution_type )
527+ self .stepsList .append (lst )
495528 return True
496529
497530 def appendStep (self , actions , expected_results , execution_type ):
498531 """ appendStep :
499532 Appends a step to the steps list
500533 """
501- list = {}
502- list ['step_number' ] = str (len (self .stepsList )+ 1 )
503- list ['actions' ] = actions
504- list ['expected_results' ] = expected_results
505- list ['execution_type' ] = str (execution_type )
506- self .stepsList .append (list )
534+ lst = {}
535+ lst ['step_number' ] = str (len (self .stepsList )+ 1 )
536+ lst ['actions' ] = actions
537+ lst ['expected_results' ] = expected_results
538+ lst ['execution_type' ] = str (execution_type )
539+ self .stepsList .append (lst )
507540 return True
508541
509542 def getProjectIDByName (self , projectName ):
510- projects = self .server .tl .getProjects ({'devKey' : self .devKey })
511- for project in projects :
512- if (project ['name' ] == projectName ):
513- result = project ['id' ]
514- else :
515- result = - 1
516- return result
517-
518- if __name__ == "__main__" :
519- myTestLinkServer = "http://YOURSERVER/testlink/lib/api/xmlrpc.php" #change
520- myDevKey = "" # Put here your devKey
521- myTestLink = TestlinkAPIClient (myTestLinkServer , myDevKey )
522- print "TestLinkAPIClient - v0.2"
523- print "@author: Olivier Renault ([email protected] )" 524- print ""
525- if myTestLink .checkDevKey () == True :
526- methodList = [method for method in TestlinkAPIClient .__dict__ ]
527- for method in methodList :
528- if method [0 :2 ] != "__" :
529- print method
530- print ""
531- else :
532- print "Incorrect DevKey."
543+ projects = self .server .tl .getProjects ({'devKey' : self .devKey })
544+ for project in projects :
545+ if (project ['name' ] == projectName ):
546+ result = project ['id' ]
547+ else :
548+ result = - 1
549+ return result
550+
551+ def __str__ (self ):
552+ message = """
553+ TestLinkAPIClient - version %s
554+ @author: Olivier Renault ([email protected] ) 555+ @author: kereval.com
556+ @author: Patrick Dassier
557+
558+ """
559+ return message % self .__VERSION__
560+
561+ class TestLink (TestLinkAPIClient ):
562+ """
563+ TestLink API library
564+ """
565+
566+ __VERSION__ = "0.1"
567+
568+ def __init__ (self , server_url , key ):
569+ """
570+ Class initialisation
571+ """
572+ super (TestLink , self ).__init__ (server_url , key )
573+
574+ def getTestCaseIDByName (self , testCaseName , testSuiteName , testProjectName ):
575+ """
576+ Find a test case by its name, by its suite and its project
577+ Suite name must not be duplicate, so only one test case must be found
578+ Return test case id if success
579+ or raise TestLinkErrors exception with error message in case of error
580+ """
581+ results = super (TestLink , self ).getTestCaseIDByName (testCaseName , testSuiteName , testProjectName )
582+ if results [0 ].has_key ("message" ):
583+ raise TestLinkErrors (results [0 ]["message" ])
584+ elif len (results ) > 1 :
585+ raise TestLinkErrors ("(getTestCaseIDByName) - Several case test found. Suite name must not be duplicate for the same project" )
586+ else :
587+ if results [0 ]["name" ] == testCaseName :
588+ return results [0 ]["id" ]
589+ raise TestLinkErrors ("(getTestCaseIDByName) - Internal server error. Return value is not expected one!" )
590+
591+
592+ def reportResult (self , testResult , testCaseName , testSuiteName , testNotes = "" , ** kwargs ):
593+ """
594+ Report results for test case
595+ Arguments are:
596+ - testResult: "p" for passed, "b" for blocked, "f" for failed
597+ - testCaseName: the test case name to report
598+ - testSuiteName: the test suite name that support the test case
599+ - testNotes: optional, if empty will be replace by a default string. To let it blank, just set testNotes to " " characters
600+ - an anonymous dictionnary with followings keys:
601+ - testProjectName: the project to fill
602+ - testPlanName: the active test plan
603+ - buildName: the active build.
604+ Raise a TestLinkErrors error with the error message in case of trouble
605+ Return the execution id needs to attach files to test execution
606+ """
607+
608+ # Check parameters
609+ for data in ["testProjectName" , "testPlanName" , "buildName" ]:
610+ if not kwargs .has_key (data ):
611+ raise TestLinkErrors ("(reportResult) - Missing key %s in anonymous dictionnary" % data )
612+
613+ # Get project id
614+ project = self .getTestProjectByName (kwargs ["testProjectName" ])
615+
616+ # Check if project is active
617+ if project ['active' ] != '1' :
618+ raise TestLinkErrors ("(reportResult) - Test project %s is not active" % kwargs ["testProjectName" ])
619+
620+ # Check test plan name
621+ plan = self .getTestPlanByName (kwargs ["testProjectName" ], kwargs ["testPlanName" ])
622+
623+ # Check is test plan is open and active
624+ if plan ['is_open' ] != '1' or plan ['active' ] != '1' :
625+ raise TestLinkErrors ("(reportResult) - Test plan %s is not active or not open" % kwargs ["testPlanName" ])
626+ # Memorise test plan id
627+ planId = plan ['id' ]
628+
629+ # Check build name
630+ build = self .getBuildByName (kwargs ["testProjectName" ], kwargs ["testPlanName" ], kwargs ["buildName" ])
631+
632+ # Check if build is open and active
633+ if build ['is_open' ] != '1' or build ['active' ] != '1' :
634+ raise TestLinkErrors ("(reportResult) - Build %s in not active or not open" % kwargs ["buildName" ])
635+
636+ # Get test case id
637+ caseId = self .getTestCaseIDByName (testCaseName , testSuiteName , kwargs ["testProjectName" ])
638+
639+ # Check results parameters
640+ if testResult not in "pbf" :
641+ raise TestLinkErrors ("(reportResult) - Test result must be 'p', 'f' or 'b'" )
642+
643+ if testNotes == "" :
644+ # Builds testNotes if empty
645+ today = date .today ()
646+ testNotes = "%s - Test performed automatically" % today .strftime ("%c" )
647+ elif testNotes == " " :
648+ #No notes
649+ testNotes = ""
650+
651+ print "testNotes: %s" % testNotes
652+ # Now report results
653+ results = self .reportTCResult (caseId , planId , kwargs ["buildName" ], testResult , testNotes )
654+ # Check errors
655+ if results [0 ]["message" ] != "Success!" :
656+ raise TestLinkErrors (results [0 ]["message" ])
657+
658+ return results [0 ]['id' ]
659+
660+ def getTestProjectByName (self , testProjectName ):
661+ """
662+ Return project
663+ A TestLinkErrors is raised in case of error
664+ """
665+ results = super (TestLink , self ).getTestProjectByName (testProjectName )
666+ if results [0 ].has_key ("message" ):
667+ raise TestLinkErrors (results [0 ]["message" ])
668+
669+ return results [0 ]
670+
671+ def getTestPlanByName (self , testProjectName , testPlanName ):
672+ """
673+ Return test plan
674+ A TestLinkErrors is raised in case of error
675+ """
676+ results = super (TestLink , self ).getTestPlanByName (testProjectName , testPlanName )
677+ if results [0 ].has_key ("message" ):
678+ raise TestLinkErrors (results [0 ]["message" ])
679+
680+ return results [0 ]
681+
682+ def getBuildByName (self , testProjectName , testPlanName , buildName ):
683+ """
684+ Return build corresponding to buildName
685+ A TestLinkErrors is raised in case of error
686+ """
687+ plan = self .getTestPlanByName (testProjectName , testPlanName )
688+ builds = self .getBuildsForTestPlan (plan ['id' ])
689+
690+ # Check if a builds exists
691+ if builds == '' :
692+ raise TestLinkErrors ("(getBuildsByName) - Builds %s does not exists for test plan %s" % (buildsName , testPlanName ))
693+
694+ # Search the correct build name in the return builds list
695+ for build in builds :
696+ if build ['name' ] == buildName :
697+ return build
698+
699+ # No build found with builName name
700+ raise TestLinkErrors ("(getBuildsByName) - Builds %s does not exists for test plan %s" % (buildsName , testPlanName ))
701+
702+
0 commit comments