QuizTrain is a framework created at Venmo allowing you to interact with TestRail's API using Swift. It supports iOS, macOS, tvOS, and watchOS.
To use QuizTrain you must have a valid TestRail license and instance to access.
QuizTrain is open source software released under the MIT License. See the LICENSE file for details.
Carthage is the recommended way to install QuizTrain. Add the following to your Cartfile
or Cartfile.private
file:
github "venmo/QuizTrain" ~> 1.0.0
See Adding frameworks to an application for further instructions. Once complete import QuizTrain
in any Swift files you wish to use QuizTrain in.
Create an ObjectAPI
to get, add, update, delete, and close items on your TestRail instance.
let objectAPI = ObjectAPI(username: "your@testRailAccount.email", secret: "your_api_key_or_password", hostname: "yourInstance.testrail.net", port: 443, scheme: "https")
Alternatively you can use API
directly if you would rather work with basic Swift types. Generally it is better to use ObjectAPI
as API
is a lower level of abstraction. For differences see comments in API.swift and ObjectAPI.swift.
Below shows a limited number of examples. For all examples see ObjectAPITests.swift.
objectAPI.getCases(inProjectWithId: 5) { (outcome) in
switch outcome {
case .failed(let error):
print(error.debugDescription)
case .succeeded(let cases):
print(cases) // Do something with cases.
}
}
let section: Section = ...
let newCase = NewCase(estimate: nil, milestoneId: nil, priorityId: nil, refs: nil, templateId: nil, title: "New Case Title", typeId: nil, customFields: nil)
objectAPI.addCase(newCase, to: section) { (outcome) in
switch outcome {
case .failed(let error):
print(error.debugDescription)
case .succeeded(let `case`):
print(`case`.title) // Do something with the newly created `case`.
}
}
var suite: Suite = ...
suite.description = "Updated description for this suite."
suite.name = "Updated name of this suite."
objectAPI.updateSuite(suite) { (outcome) in
switch outcome {
case .failed(let error):
print(error.debugDescription)
case .succeeded(let updatedSuite):
print(updatedSuite.description) // "Updated description for this suite."
print(updatedSuite.name) // "Updated name of this suite."
}
}
let section: Section = ...
objectAPI.deleteSection(section) { (outcome) in
switch outcome {
case .failed(let error):
print(error.debugDescription)
case .succeeded(_): // nil on successful deletes
print("The section has been successfully deleted.")
}
}
let plan: Plan = ...
objectAPI.closePlan(plan) { (outcome) in
switch outcome {
case .failed(let error):
print(error.debugDescription)
case .succeeded(let closedPlan):
print(closedPlan.isCompleted) // true
print(closedPlan.completedOn) // timestamp
}
}
let milestone: Milestone = ...
milestone.parent(objectAPI) { (outcome) in
switch outcome {
case .failed(let error):
print(error.debugDescription)
case .succeeded(let optionalParent):
if let parent = optionalParent {
print("Milestone \(milestone.id) has a parent with an id of \(parent.id).")
} else {
print("Milestone \(milestone.id) does not have a parent.")
}
}
}
let filters = [Filter(named: "is_completed", matching: true)]
objectAPI.getRuns(inProjectWithId: 3, filteredBy: filters) { (outcome) in
switch outcome {
case .failed(let error):
print(error.debugDescription)
case .succeeded(let completedRuns):
for completedRun in completedRuns {
print(completedRun.isCompleted) // true
}
}
}
let project: Project = ...
let filters = [Filter(named: "offset", matching: 3),
Filter(named: "limit", matching: 5)]
objectAPI.getPlans(in: project, filteredBy: filters) { (outcome) in
switch outcome {
case .failed(let error):
print(error.debugDescription)
case .succeeded(let plans): // There will be 5 or less plans.
for plan in plans {
print(plan.name)
}
}
}
It is up to the consumer of QuizTrain to handle all errors. However ObjectAPI
will handle 429 Too Many Request (rate limit reached) errors automatically unless you set .handle429TooManyRequestErrors
to false
.
Errors are defined here:
- API.swift in the Errors section.
- ObjectAPI.swift in the Errors section.
You can handle errors two ways:
- Simply by using
error.debugDescription
to print a rich description of an error.- Provided by errors conforming to
DebugDescription
.
- Provided by errors conforming to
- Advanced by
switch
'ing on an error.
Simple is best for debugging and logging. Advanced is best for everything else.
All API and ObjectAPI errors conform to DebugDescription
. If these errors print a URLRequest
through this protocol then its AUTHORIZATION
header will be stripped to avoid exposing your TestRail credentials.
This shows both simple and advanced error handling when adding a new Case to a Section.
let newCase = NewCase(estimate: nil, milestoneId: nil, priorityId: nil, refs: nil, templateId: nil, title: "New Case Title", typeId: nil, customFields: nil)
objectAPI.addCase(newCase, toSectionWithId: 5) { (outcome) in
switch outcome {
case .failed(let error):
print(error.debugDescription)
case .succeeded(let `case`):
print(`case`.title) // Do something with the newly created `case`.
}
}
objectAPI.addCase(newCase, toSectionWithId: 5) { (outcome) in
switch outcome {
case .failed(let error):
switch error {
case .apiError(let apiError): // API.RequestError
switch apiError {
case .error(let request, let error):
print(request)
print(error)
case .invalidResponse(let request, let response):
print(request)
print(response)
case .nilResponse(let request):
print(request)
}
case .dataProcessingError(let dataProcessingError): // ObjectAPI.DataProcessingError
switch dataProcessingError {
case .couldNotConvertDataToJSON(let data, let error):
print(data)
print(error)
case .couldNotDeserializeFromJSON(let objectType, let json):
print(objectType)
print(json)
case .invalidJSONFormat(let json):
print(json)
}
case .objectConversionError(let objectConversionError): // ObjectAPI.ObjectConversionError
switch objectConversionError {
case .couldNotConvertObjectsToData(let objects, let json, let error):
print(objects)
print(json)
print(error)
case .couldNotConvertObjectToData(let object, let json, let error):
print(object)
print(json)
print(error)
}
case .statusCodeError(let statusCodeError): // ObjectAPI.StatusCodeError
switch statusCodeError {
case .clientError(let clientError): // ObjectAPI.ClientError
print(clientError.message)
print(clientError.statusCode)
print(clientError.requestResult.request)
print(clientError.requestResult.response)
print(clientError.requestResult.data)
case .otherError(let otherError): // API.RequestResult
print(otherError.request)
print(otherError.response)
print(otherError.data)
case .serverError(let serverError): // ObjectAPI.ServerError
print(serverError.message)
print(serverError.statusCode)
print(serverError.requestResult.request)
print(serverError.requestResult.response)
print(serverError.requestResult.data)
}
}
case .succeeded(let `case`):
print(`case`.title) // Do something with the newly created `case`.
}
}
See the QuizTrainTests Readme.