Sitemap

Integrate Cypress report with Xray & Jira API

16 min readFeb 2, 2025

In this blog, we’ll show you how to automate uploading test results to Jira, just like the example shown in the picture.

Press enter or click to view image in full size

Why Choose Xray for Cypress Test Results?

I chose Xray to display Cypress test results because it offers multiple reporting options, similar to TestRail, Qase, and Zephyr (another Jira plugin). However, what makes Xray stand out is that it fully integrates with Jira, simplifying the entire test management process.

Nowadays, most organizations use Jira to track work processes. Development and QA teams create issues, user stories, and tasks there, so keeping test results in the same place makes it easier for everyone to see and stay updated. Instead of switching between tools, you can check test results right where the work is happening.

With Xray, you can:
✅ Store and manage test cases
✅ Organize test plans
✅ Track test results and generate reports
✅ Connect everything with CI/CD pipelines

This way, everything related to testing stays in one place, making it easier to manage and follow up on.

Understanding the Automated Workflow

  1. Run Cypress test cases to generate JSON and HTML reports.
  2. Convert the Mochawesome JSON report into Xray format.
  3. Upload test results to the test execution card in Jira.

Jira Automation Rules

  • A new test execution card is always created with a title format based on the test suite name and date (e.g., Web — Product Page Tests 01/02/2025).
  • If all test cases PASSED, move the Jira card to the “Done” status.
  • If at least one test case FAILED, move the Jira card to the “To Do” status.
  • Attach the report as a ZIP file to the test execution card.
  • Link defect cards to the corresponding failed test cases.

STEP 1 : Set up Cypress report

For Cypress reporting, I recommend using the Mochawesome plugin. To generate reports in multiple formats such as JSON, JUnit, and HTML, you’ll need to install the following dependencies:

npm install --save-dev cypress-multi-reporters mochawesome mocha-junit-reporter mochawesome-merge mochawesome-report-generator

Why Do We Need These Packages?

  • cypress-multi-reporters — The main reporter that enables multiple reporters to run simultaneously. That’s why we set reporter: 'cypress-multi-reporters' in the Cypress configuration.
  • mochawesome — Generates JSON reports for each test file.
  • mocha-junit-reporter — Produces JUnit XML reports, which are commonly used in CI/CD integrations.
  • mochawesome-merge — Merges multiple JSON reports into a single file. (You’ll need this for consolidated reporting.)
  • mochawesome-report-generator — Converts JSON reports into an HTML format. (You’ll need this for visual reports.)

Enabling Mochawesome in Cypress

To enable mochawesome reporting, update your cypress.config.ts file as follows:

export default defineConfig({
reporter: 'cypress-multi-reporters',
reporterOptions: {
// Enables both mochawesome and junit reporters
reporterEnabled: 'mochawesome, mocha-junit-reporter',

// Mochawesome specific options
mochawesomeReporterOptions: {
reportDir: 'reports/mocha',
overwrite: false, // Don't overwrite existing reports
html: false, // Don't generate HTML during test run
json: true, // Generate JSON reports
charts: true,
embeddedScreenshots: true,
inlineAssets: true,
reportPageTitle: 'Test Execution Report',
quiet: true,
},

// JUnit specific options
mochaJunitReporterReporterOptions: {
mochaFile: 'reports/junit/results-[hash].xml',
},
}
});

Set up Commands to Generate and Merge Reports in package.json

"report:merge:web": "mochawesome-merge web/reports/mocha/*.json > web/reports/mocha/combined.json || echo 'No Web reports to merge'",
"report:generate:web": "marge web/reports/mocha/combined.json --reportDir web/reports/mocha --reportFilename index --inline --charts --reportTitle \"Web UI Test Results\"",

Understanding the Commands

When running multiple test suites simultaneously, Mochawesome generates multiple JSON report files inside the web/reports/mocha/ directory. To combine them into one file and make further processing easier, follow these steps:

  1. Merge JSON Reports — Since multiple JSON reports are created, we first merge them into a single file using:
mochawesome-merge web/reports/mocha/*.json > web/reports/mocha/combined.json

(If no reports are found, the command prints “No Web reports to merge.”)

2. Generate the HTML Report — After merging, we create an HTML report from the combined JSON file by running:

marge web/reports/mocha/combined.json --reportDir web/reports/mocha --reportFilename index --inline --charts --reportTitle "Web UI Test Results"

This produces a index.html file insideweb/reports/mocha/ making it easier to review the test results in a structured format.

Additionally, you can attach screenshots and context to the HTML report, making it easier to inspect test results and verify functionality.

In your cypress/support/e2e.js file, add this code:

import addContext from 'mochawesome/addContext';

Cypress.on('uncaught:exception', () => false);

declare global {
namespace Cypress {
interface Chainable {
addTestContext(value: string): Chainable<void>;
}
}
}

Cypress.Commands.add('addTestContext', (context: string) => {
cy.once('test:after:run', (test) => addContext({ test }, context));
});

afterEach(function () {
const test = this.currentTest;
const screenshotName = `${test.fullTitle().replace(/\s+/g, '_')}`;
const specFile = Cypress.spec.name;

cy.screenshot(screenshotName, {
capture: 'fullPage',
overwrite: true,
scale: false
});
cy.addTestContext(`./assets/${specFile}/${screenshotName}.png`);
});

Example usage in the test script:

Press enter or click to view image in full size

HTML report result:

Press enter or click to view image in full size

Before converting the file format, we need to create an API token for the Jira REST API, as well as a client ID and client secret for the Xray API.

STEP 2 : Setup authentication token for API

  1. For Jira API token, navigate to https://id.atlassian.com/manage-profile/security/api-tokens

Click ‘Create API token’ and set the expiration date

Press enter or click to view image in full size
Press enter or click to view image in full size

2. For Xray API token:

First, you go to app market place to add Xray to your jira:

Press enter or click to view image in full size

Then, Navigate to setting > app > API token under Xray

Press enter or click to view image in full size
Press enter or click to view image in full size
Press enter or click to view image in full size

Now, on your local project, create .env file with all credentials like this:

XRAY_CLIENT_ID=<---your_xray_client_id--->
XRAY_CLIENT_SECRET=<---your_xray_client_secret--->
JIRA_EMAIL=<---your_jira_email--->
JIRA_API_TOKEN=<---your_jira_api_token--->

STEP 3 : Mapping Jira issue type to Xray configuration and creating test cases

Click on ‘Configure Project’ under Xray on the left side of your project and choose the project name that you want to configure.

Press enter or click to view image in full size
Press enter or click to view image in full size

Here, you need to select the issue types you want to represent as a test case, test plan, and test execution.

Press enter or click to view image in full size

At project settings, you can create an issue type name here.

Press enter or click to view image in full size

First, click ‘Create’ on the top to create issue type ‘automate’ for test case (the same issue type that you mapping on Xray configure)

Press enter or click to view image in full size
Press enter or click to view image in full size

In the test script, include the Jira issue number in the test case title to make it easier to filter and associate results later.

Press enter or click to view image in full size

Next, create an ‘Epic’ issue type to represent the test plan (the same issue type you mapped in the Xray configuration).

Press enter or click to view image in full size

Navigate to the test plan that we just created and add all test cases to that test plan.

Press enter or click to view image in full size
Press enter or click to view image in full size
Press enter or click to view image in full size

The total number of test executions comes from the total ‘Test’ issue types that we mapped in the Xray configuration. When you click the icon next to the latest status, it will display the Jira issue number along with the result for each test run.

Press enter or click to view image in full size

STEP 4 : Convert Mochawesome JSON report into Xray format

Let’s take a look at the Xray documentation to check the required JSON format.
Here, we see that we need to send the results to https://xray.cloud.getxray.app/api/v1/import/execution. The request body structure should include the testPlanKey and testKey, which correspond to our test case number and status, as shown below.

Press enter or click to view image in full size
https://docs.getxray.app/display/XRAYCLOUD/Import+Execution+Results+-+REST

Make the javascript to read the JSON file into a JSON object and make the data into Xray format.

const fs = require('fs');

function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
}).replace(/\//g, '/');
}

function convertMochaToXray(mochaResultPath) {
// Read Mochawesome JSON
const mochaResults = JSON.parse(fs.readFileSync(mochaResultPath, 'utf8'));

// Get the suite title from the first suite
let suiteTitle = "Test Execution";
if (mochaResults.results[0]?.suites[0]?.title) {
suiteTitle = mochaResults.results[0].suites[0].title;
}

// Format the date from start time
const executionDate = formatDate(mochaResults.stats.start);

const xrayResults = {
info: {
summary: `${suiteTitle} ${executionDate}`,
description: "Automated test execution from Cypress",
user: "thanasornsawan varathanamongkolchai",
startDate: mochaResults.stats.start,
finishDate: mochaResults.stats.end,
testPlanKey: "TP-30" //Use your test plan key
},
tests: []
};

// Convert each test result
mochaResults.results.forEach(result => {
result.suites.forEach(suite => {
suite.tests.forEach(test => {
// Extract test key from title (e.g., "TP-27" from "TP-27 should search...")
const testKeyMatch = test.title.match(/^(TP-\d+)/);
const testKey = testKeyMatch ? testKeyMatch[1] : null;

if (!testKey) {
console.warn(`Warning: No test key found in title: ${test.title}`);
return;
}

const xrayTest = {
testKey: testKey,
start: mochaResults.stats.start,
finish: mochaResults.stats.end,
comment: test.fullTitle,
status: test.state.toUpperCase()
};

xrayResults.tests.push(xrayTest);
});
});
});

// Write Xray format JSON
fs.writeFileSync('xray-results.json', JSON.stringify(xrayResults, null, 2));
console.log('Conversion completed. Results written to xray-results.json');
}

// Usage
// combined.json is Mochawesome JSON report
try {
convertMochaToXray('reports/combined.json');
} catch (error) {
console.error('Error during conversion:', error);
}

Refer to the JSON report structure as shown in the picture below:

Press enter or click to view image in full size

Run the convert script:

node scripts/convert_mocha_xray.js

Result after converted to the xray-results.json file:

{
"info": {
"summary": "Web - Product Page Tests 01/02/2025",
"description": "Automated test execution from Cypress",
"user": "thanasornsawan varathanamongkolchai",
"startDate": "2025-02-01T16:54:28.550Z",
"finishDate": "2025-02-01T16:55:19.318Z",
"testPlanKey": "TP-30"
},
"tests": [
{
"testKey": "TP-27",
"start": "2025-02-01T16:54:28.550Z",
"finish": "2025-02-01T16:55:19.318Z",
"comment": "Product Page Tests TP-27 should search for Long Nose Pliers and verify out of stock behavior",
"status": "PASSED"
},
{
"testKey": "TP-28",
"start": "2025-02-01T16:54:28.550Z",
"finish": "2025-02-01T16:55:19.318Z",
"comment": "Product Page Tests TP-28 should verify related products match main product category",
"status": "PASSED"
},
{
"testKey": "TP-29",
"start": "2025-02-01T16:54:28.550Z",
"finish": "2025-02-01T16:55:19.318Z",
"comment": "Product Page Tests TP-29 should adjust quantity on product detail page and verify cart page match",
"status": "PASSED"
}
]
}

STEP 5 : Upload test results to Jira

To update the test execution results (PASSED/FAILED), we first need to obtain an authentication token using XRAY_CLIENT_ID and XRAY_CLIENT_SECRET. This token is required to upload the JSON test results to the Xray API endpoint:

👉 https://xray.cloud.getxray.app/api/v1/import/execution

import requests
import json
from datetime import datetime
import os
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

def get_xray_token(client_id, client_secret):
url = "https://xray.cloud.getxray.app/api/v2/authenticate"

credentials = {
"client_id": client_id,
"client_secret": client_secret
}

headers = {
"Content-Type": "application/json"
}

response = requests.post(url, headers=headers, json=credentials)
print(f"Authentication Status Code: {response.status_code}")

if response.status_code != 200:
raise Exception(f"Authentication failed with status code: {response.status_code}\nResponse: {response.text}")

return response.text.strip('"')

def upload_test_results(xray_token, json_file_path):
# Read the JSON results
try:
with open(json_file_path, 'r') as file:
test_results = json.load(file)
print(f"Successfully loaded test results from {json_file_path}")
print(f"Test results content: {json.dumps(test_results, indent=2)}")
except Exception as e:
print(f"Error loading JSON file: {e}")
raise

# Xray API endpoint for test execution import
url = "https://xray.cloud.getxray.app/api/v1/import/execution"

headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {xray_token}"
}

print(f"Sending request to Xray...")

response = requests.post(url, headers=headers, json=test_results)

print(f"Upload Status Code: {response.status_code}")
print(f"Upload Response Headers: {response.headers}")
print(f"Upload Response Body: {response.text}")

if response.status_code != 200:
raise Exception(f"Upload failed with status code: {response.status_code}\nResponse: {response.text}")

return response.json()

if __name__ == "__main__":
# Your credentials
XRAY_CLIENT_ID = os.getenv('XRAY_CLIENT_ID')
XRAY_CLIENT_SECRET = os.getenv('XRAY_CLIENT_SECRET')
JSON_FILE_PATH = "xray-results.json"

try:
# Get Xray token
print("Getting Xray token...")
xray_token = get_xray_token(XRAY_CLIENT_ID, XRAY_CLIENT_SECRET)
print("Successfully got Xray token")

# Upload results
print("Uploading test results...")
result = upload_test_results(xray_token, JSON_FILE_PATH)

print("Upload successful!")
print("Result:", result)
except Exception as e:
print(f"Error occurred: {str(e)}")

You can see that the Xray API responds with the test execution key in the response.

Press enter or click to view image in full size

We want to add an automation rule to the Jira card to help focus test runs that have failed issues by:

  • If all test cases PASSED, move the Jira card to the “Done” status.
  • If at least one test case FAILED, move the Jira card to the “To Do” status.
def transition_issue(issue_key, jira_email, jira_token, target_status):
"""
Get available transitions and move issue to target status
"""
base_url = "https://thanasornsawan1.atlassian.net/rest/api/3"
auth = (jira_email, jira_token)
headers = {
"Accept": "application/json",
"Content-Type": "application/json"
}

# First, get all available transitions
transitions_url = f"{base_url}/issue/{issue_key}/transitions"
transitions_response = requests.get(transitions_url, headers=headers, auth=auth)

if transitions_response.status_code != 200:
print(f"Error getting transitions: {transitions_response.text}")
return False

transitions = transitions_response.json()['transitions']

# Find the transition ID for our target status
transition_id = None
for transition in transitions:
if transition['to']['name'].lower() == target_status.lower():
transition_id = transition['id']
break

if not transition_id:
print(f"Could not find transition to status: {target_status}")
return False

# Perform the transition
transition_payload = {
"transition": {
"id": transition_id
}
}

response = requests.post(
transitions_url,
headers=headers,
json=transition_payload,
auth=auth
)

if response.status_code not in [200, 204]:
print(f"Error transitioning issue: {response.text}")
return False

print(f"Successfully transitioned {issue_key} to {target_status}")
return True

Usage transition_issue function:

First, read the json file ‘xray-results.json’ into a json object.

 try:
with open(json_file_path, 'r') as file:
test_results = json.load(file)
print(f"Successfully loaded test results from {json_file_path}")
except Exception as e:
print(f"Error loading JSON file: {e}")
raise

Get the test execution key from the response.

result = response.json()
new_execution_key = result.get('key')

Filter result ‘PASSED’ or ‘FAILED’ to send target status ‘To Do’ or ‘Done’ to the transition_issue function.

has_failures = any(test.get('status') != 'PASSED' for test in test_results.get('tests', []))

target_status = "To Do" if has_failures else "Done"
print(f"Moving {new_execution_key} to {target_status} based on test results...")

# Transition the newly created test execution
transition_success = transition_issue(new_execution_key, jira_email, jira_token, target_status)
if transition_success:
print(f"Successfully moved {new_execution_key} to {target_status}")
else:
print(f"Failed to move {new_execution_key} to {target_status}")
Press enter or click to view image in full size
Press enter or click to view image in full size

Next, we want to upload the HTML report as a zip file to the test execution card.

def create_report_zip(report_dir, zip_path):
"""
Create a zip file containing the entire report directory
"""
try:
with ZipFile(zip_path, 'w') as zipf:
# Walk through the directory
for root, dirs, files in os.walk(report_dir):
for file in files:
file_path = os.path.join(root, file)
arc_name = os.path.relpath(file_path, report_dir)
zipf.write(file_path, arc_name)
print(f"Successfully created zip file at {zip_path}")
return True
except Exception as e:
print(f"Error creating zip file: {str(e)}")
return False

def attach_report_to_jira(issue_key, zip_path, jira_email, jira_token):
"""
Attach zipped report to Jira issue
"""
base_url = "https://thanasornsawan1.atlassian.net/rest/api/3"
auth = (jira_email, jira_token)

try:
with open(zip_path, 'rb') as file:
files = {
'file': (os.path.basename(zip_path), file, 'application/zip')
}

attach_url = f"{base_url}/issue/{issue_key}/attachments"
headers = {
"Accept": "application/json",
"X-Atlassian-Token": "no-check"
}

response = requests.post(
attach_url,
headers=headers,
files=files,
auth=auth
)

if response.status_code == 200:
print(f"Successfully attached report zip to {issue_key}")
return True
else:
print(f"Failed to attach report zip: {response.text}")
return False

except Exception as e:
print(f"Error attaching report zip: {str(e)}")
return False

Usage attach_report_to_jira function:

report_dir = 'web/reports/mocha'  # Directory containing index.html and assets

# Create temporary zip file
zip_path = 'test_report.zip'
if not create_report_zip(report_dir, zip_path):
raise Exception("Failed to create report zip file")

result = response.json()
new_execution_key = result.get('key')

attach_success = attach_report_to_jira(new_execution_key, zip_path, jira_email, jira_token)
Press enter or click to view image in full size
Press enter or click to view image in full size

Next, we want to add the parent card as the test plan number (in our case, TP-30) to the test execution card.

def link_to_test_plan(issue_key, test_plan_key, jira_email, jira_token):
"""
Set test plan as parent of the test execution
"""
base_url = "https://thanasornsawan1.atlassian.net/rest/api/3"
auth = (jira_email, jira_token)

update_url = f"{base_url}/issue/{issue_key}"
headers = {
"Accept": "application/json",
"Content-Type": "application/json"
}

# Simple payload with parent field
payload = {
"fields": {
"parent": {
"key": test_plan_key
}
}
}

try:
response = requests.put(
update_url,
headers=headers,
json=payload,
auth=auth
)

if response.status_code in [200, 204]:
print(f"Successfully set {test_plan_key} as parent of {issue_key}")
return True
else:
print(f"Failed to set parent: {response.text}")
return False

except Exception as e:
print(f"Error setting parent: {str(e)}")
return False

You can see the JSON request body structure from the Jira document here:

Press enter or click to view image in full size
https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-post
Press enter or click to view image in full size
Press enter or click to view image in full size

Final script:

import requests
import json
import os
import shutil
from zipfile import ZipFile
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

def get_xray_token(client_id, client_secret):
"""
Get authentication token from Xray
"""
url = "https://xray.cloud.getxray.app/api/v2/authenticate"

credentials = {
"client_id": client_id,
"client_secret": client_secret
}

headers = {
"Content-Type": "application/json"
}

response = requests.post(url, headers=headers, json=credentials)
print(f"Authentication Status Code: {response.status_code}")

if response.status_code != 200:
raise Exception(f"Authentication failed with status code: {response.status_code}\nResponse: {response.text}")

return response.text.strip('"')

def create_report_zip(report_dir, zip_path):
"""
Create a zip file containing the entire report directory
"""
try:
with ZipFile(zip_path, 'w') as zipf:
# Walk through the directory
for root, dirs, files in os.walk(report_dir):
for file in files:
file_path = os.path.join(root, file)
arc_name = os.path.relpath(file_path, report_dir)
zipf.write(file_path, arc_name)
print(f"Successfully created zip file at {zip_path}")
return True
except Exception as e:
print(f"Error creating zip file: {str(e)}")
return False

def attach_report_to_jira(issue_key, zip_path, jira_email, jira_token):
"""
Attach zipped report to Jira issue
"""
base_url = "https://thanasornsawan1.atlassian.net/rest/api/3"
auth = (jira_email, jira_token)

try:
with open(zip_path, 'rb') as file:
files = {
'file': (os.path.basename(zip_path), file, 'application/zip')
}

attach_url = f"{base_url}/issue/{issue_key}/attachments"
headers = {
"Accept": "application/json",
"X-Atlassian-Token": "no-check"
}

response = requests.post(
attach_url,
headers=headers,
files=files,
auth=auth
)

if response.status_code == 200:
print(f"Successfully attached report zip to {issue_key}")
return True
else:
print(f"Failed to attach report zip: {response.text}")
return False

except Exception as e:
print(f"Error attaching report zip: {str(e)}")
return False

def transition_issue(issue_key, jira_email, jira_token, target_status):
"""
Get available transitions and move issue to target status
"""
base_url = "https://thanasornsawan1.atlassian.net/rest/api/3"
auth = (jira_email, jira_token)
headers = {
"Accept": "application/json",
"Content-Type": "application/json"
}

# First, get all available transitions
transitions_url = f"{base_url}/issue/{issue_key}/transitions"
transitions_response = requests.get(transitions_url, headers=headers, auth=auth)

if transitions_response.status_code != 200:
print(f"Error getting transitions: {transitions_response.text}")
return False

transitions = transitions_response.json()['transitions']

# Find the transition ID for our target status
transition_id = None
for transition in transitions:
if transition['to']['name'].lower() == target_status.lower():
transition_id = transition['id']
break

if not transition_id:
print(f"Could not find transition to status: {target_status}")
return False

# Perform the transition
transition_payload = {
"transition": {
"id": transition_id
}
}

response = requests.post(
transitions_url,
headers=headers,
json=transition_payload,
auth=auth
)

if response.status_code not in [200, 204]:
print(f"Error transitioning issue: {response.text}")
return False

print(f"Successfully transitioned {issue_key} to {target_status}")
return True

def update_issue_description(issue_key, jira_email, jira_token):
"""
Update issue description with instructions for the zipped report
"""
base_url = "https://thanasornsawan1.atlassian.net/rest/api/3"
auth = (jira_email, jira_token)

description = {
"version": 1,
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Automated test execution report from Cypress"
}
]
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "To view the detailed HTML report:"
}
]
},
{
"type": "bulletList",
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "1. Download the attached zip file"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "2. Extract the zip file"
}
]
}
]
},
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "3. Open index.html in your browser"
}
]
}
]
}
]
}
]
}

try:
update_url = f"{base_url}/issue/{issue_key}"
headers = {
"Accept": "application/json",
"Content-Type": "application/json"
}

payload = {
"update": {},
"fields": {
"description": description
}
}

response = requests.put(
update_url,
headers=headers,
json=payload,
auth=auth
)

if response.status_code == 204:
print(f"Successfully updated description for {issue_key}")
return True
else:
print(f"Failed to update description: {response.text}")
return False

except Exception as e:
print(f"Error updating description: {str(e)}")
return False

def link_to_test_plan(issue_key, test_plan_key, jira_email, jira_token):
"""
Set test plan as parent of the test execution
"""
base_url = "https://thanasornsawan1.atlassian.net/rest/api/3"
auth = (jira_email, jira_token)

update_url = f"{base_url}/issue/{issue_key}"
headers = {
"Accept": "application/json",
"Content-Type": "application/json"
}

# Simple payload with parent field
payload = {
"fields": {
"parent": {
"key": test_plan_key
}
}
}

try:
response = requests.put(
update_url,
headers=headers,
json=payload,
auth=auth
)

if response.status_code in [200, 204]:
print(f"Successfully set {test_plan_key} as parent of {issue_key}")
return True
else:
print(f"Failed to set parent: {response.text}")
return False

except Exception as e:
print(f"Error setting parent: {str(e)}")
return False

def upload_test_results(xray_token, json_file_path, report_dir, jira_email, jira_token):
try:
with open(json_file_path, 'r') as file:
test_results = json.load(file)
print(f"Successfully loaded test results from {json_file_path}")
except Exception as e:
print(f"Error loading JSON file: {e}")
raise

# Get test plan key from test results
test_plan_key = test_results.get('info', {}).get('testPlanKey')

# Create temporary zip file
zip_path = 'test_report.zip'
if not create_report_zip(report_dir, zip_path):
raise Exception("Failed to create report zip file")

try:
# Upload to Xray
url = "https://xray.cloud.getxray.app/api/v1/import/execution"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {xray_token}"
}

print("Sending request to Xray...")
response = requests.post(url, headers=headers, json=test_results)

if response.status_code != 200:
raise Exception(f"Upload failed with status code: {response.status_code}")

result = response.json()
new_execution_key = result.get('key')

if new_execution_key:
print(f"New test execution created: {new_execution_key}")

# Link to test plan
if test_plan_key:
print(f"Linking test execution to test plan {test_plan_key}...")
link_success = link_to_test_plan(new_execution_key, test_plan_key, jira_email, jira_token)
if link_success:
print(f"Successfully linked {new_execution_key} to test plan {test_plan_key}")
else:
print(f"Failed to link {new_execution_key} to test plan {test_plan_key}")

# Attach zip report
print("Attaching zipped report...")
attach_success = attach_report_to_jira(new_execution_key, zip_path, jira_email, jira_token)

# Update description
if attach_success:
print("Updating issue description...")
update_issue_description(new_execution_key, jira_email, jira_token)

# Handle status transition
has_failures = any(test.get('status') != 'PASSED' for test in test_results.get('tests', []))
target_status = "To Do" if has_failures else "Done"
print(f"Moving {new_execution_key} to {target_status} based on test results...")

transition_success = transition_issue(new_execution_key, jira_email, jira_token, target_status)
if transition_success:
print(f"Successfully moved {new_execution_key} to {target_status}")
else:
print(f"Failed to move {new_execution_key} to {target_status}")

return result
finally:
# Clean up temporary zip file
if os.path.exists(zip_path):
os.remove(zip_path)
print("Cleaned up temporary zip file")

if __name__ == "__main__":
# Constants
JSON_FILE_PATH = 'xray-results.json'
REPORT_DIR = 'web/reports/mocha' # Directory containing index.html and assets

# Get credentials from environment variables
XRAY_CLIENT_ID = os.getenv('XRAY_CLIENT_ID')
XRAY_CLIENT_SECRET = os.getenv('XRAY_CLIENT_SECRET')
JIRA_EMAIL = os.getenv('JIRA_EMAIL')
JIRA_API_TOKEN = os.getenv('JIRA_API_TOKEN')

# Validate required environment variables
required_vars = ['XRAY_CLIENT_ID', 'XRAY_CLIENT_SECRET', 'JIRA_EMAIL', 'JIRA_API_TOKEN']
missing_vars = [var for var in required_vars if not os.getenv(var)]

if missing_vars:
raise Exception(f"Missing required environment variables: {', '.join(missing_vars)}")

try:
print("Getting Xray token...")
xray_token = get_xray_token(XRAY_CLIENT_ID, XRAY_CLIENT_SECRET)
print("Successfully got Xray token")

print("Uploading test results...")
result = upload_test_results(xray_token, JSON_FILE_PATH, REPORT_DIR, JIRA_EMAIL, JIRA_API_TOKEN)

print("Process completed!")
except Exception as e:
print(f"Error occurred: {str(e)}")

If you find a bug and want to link a defect card to the test execution card, you need to first map the defect issue type in the Xray configuration, like this.

Press enter or click to view image in full size

Create Jira issue type ‘Bug’ for defect.

Press enter or click to view image in full size

Your Xray JSON should look like this:

Press enter or click to view image in full size
Press enter or click to view image in full size

Refer to my personal project:

Thanks for reading, and I hope you found this article to be helpful.

--

--

Ploy Thanasornsawan
Ploy Thanasornsawan

Written by Ploy Thanasornsawan

Sharing knowledge about security and automation techniques.

No responses yet