TestProject Forum
Powered by leading experts in the test automation community

[SOLVED] Post TestProject Execution results to an API

Hi all, I am just getting started with using TestProject and I’m wondering how I can achieve this:

  1. Run a test on TestProject
  2. Post test result (Pass/Fail) to Zephyr Scale API - a test management addon for JIRA

It doesn’t look like I can do this without exporting to code, am I right?

Hello @Philip.Wong, thanks for reaching out.

you can do it using TestProject API,
TestProject exposes a RESTful API that allows scheduling and triggering automation, get status, and retrieve testing results.
with TestProject API, you can integrate your TestProject tests/jobs with any platform you wish.

so you can whether execute your test/job from TestProject Platform or from TestProject API using these endpoints:

/v2/projects/{projectId}/jobs/{jobId}/run
/v2/projects/{projectId}/tests/{testId}/run

and get the reports using these endpoints:

/v2/projects/{projectId}/jobs/{jobId}/reports
/v2/projects/{projectId}/tests/{testId}/reports

you will be able to find the execution result (pass/failed) inside the responses of the API calls

1 Like

Thanks @meidan.nasi, let me clarify my situation. Let’s say I create a test like this from the TestProject platform (just an example):

  1. Go to a webpage and click on a button
  2. If the button exists, it passes and if it doesn’t, it fails
  3. Use TestProject API to get the test result of the current test
  4. Send those results to my JIRA addon API

In step 3, isn’t the test still running so it doesn’t know if it has passed or not? Therefore I can’t easily achieve what I am looking to do. I have to do things like this:

  1. Create Test A which is the actual test
  2. Test B is the checking of API etc. which checks Test A
  3. If I have Test X Y Z then I have to put them all in a job like this?
    1. Test A, Test B, Test X, Test B, Test Y, Test B, Test Z, Test B…

The ideal case is if I can do this from the platform and set these actions for each test but I guess this is not an existing feature:

  1. On test failure - post to my JIRA addon API the failed results OR
  2. On test success - post to my JIRA addon API the success results

Please correct me if I’m wrong on these points.

Hello Philip,

Yes, you are correct.
You can do it with 2 jobs with the following tests assigned to them:

Job 1:

  • your required tests.

Job 2:

A test which performs the following steps:

  • trigger an API call to /v2/projects/{projectId}/jobs/{jobId}/reports for retrieving job reports, extract execution results from the response object, save it into a parameter.
  • Trigger an API call to your Jira addon with the results parameter you set above.

Jobs are been executed sequentially, so once job 1 finishes, you can retrieve its reports using job 2.

If you are familiar with coding, you can also code a script, which triggers an API call for running your jobs, retrieving the results, and deliver it to your JIRA API.

Here is an example Python script:

import requests
from threading import Timer
import time
import datetime

project_id = "PROJECT_ID"
base = "https://api.testproject.io"
header = {"Authorization": "YOUR_API_KEY"}
jobs_ids = ["JOB1_ID", "JOB2_ID"]
jobs_names = {}
executions_id = {}
interval = 120  # Interval in seconds, set it to your liking.
job_states = ["Failed", "Passed", "Skipped", "Suspended", "Error", "Aborted"]
summary = {}
starting_times = {}
body = {"queue": "true"}
counter = 0


def execute_job():
    for job_id in jobs_ids:
        url = f"{base}/v2/projects/{project_id}/jobs/{job_id}/run"
        response = requests.post(url, headers=header, data=body)
        job_name = jobs_names[job_id]
        try:
            data = response.json()
            if 'id' in data:
                print(f"Starting '{job_name}' ...")
                executions_id[job_id] = data['id']
        except:
            print(f"Job '{job_name}' can not execute, status code is {response.status_code}.")
        time.sleep(5)


def reset_starting_times():
    for job_name in jobs_names.values():
        starting_times[job_name] = None


def get_state_with_timer(job_id, execution_id):
    global counter
    url = f"{base}/v2/projects/{project_id}/jobs/{job_id}/executions/{execution_id}/state"
    state = requests.get(url, headers=header)
    job_name = get_job_name(job_id)
    t = Timer(interval, get_state_with_timer, [job_id, execution_id])
    t.start()
    data = state.json()
    state_result = data['state']
    if state_result == "Executing" and starting_times[job_name] == None:
        starting_times[job_name] = datetime.datetime.now()
    print(f"Job: {job_name}, State: {state_result}")
    # Checking interval countdown --
    counter += 1
    if counter == len(jobs_ids):
        print("-----------------------------")
        counter = 0
    # Checking interval countdown --
    if state_result in job_states:
        t.cancel()
        summary[job_name] = state_result
        if len(summary) == len(executions_id):
            show_summary()


def get_jobs_names():
    for job_id in jobs_ids:
        jobs_names[job_id] = get_job_name(job_id)


def get_state(job_id, execution_id):
    url = f"{base}/v2/projects/{project_id}/jobs/{job_id}/executions/{execution_id}/state"
    state = requests.get(url, headers=header)
    try:
        return state.json()['state']
    except:
        print(f"Can not retrieve status, status code is {state.status_code}.")


def start_interval():
    for job_id, execution_id in executions_id.items():
        get_state_with_timer(job_id, execution_id)


def get_job_name(job_id):
    url = f"{base}/v2/projects/{project_id}/jobs/{job_id}/"
    response = requests.get(url, headers=header)
    try:
        return response.json()['name']
    except:
        print(f"Could not retrieve Job name, status code is {response.status_code}.")


def show_summary():
    print("\nSummary:\n")
    for job_name, final_state in summary.items():
        print(f"Job \'{job_name}\' has finished with the result of: {final_state}")
        print(
            f"Executed on {starting_times[job_name]} {time.tzname}, with the execution time of: {datetime.datetime.now() - starting_times[job_name]}\n")


get_jobs_names()
reset_starting_times()
execute_job()  # Start executing jobs
2 Likes