UNPKG

@xray-app/xray-automation

Version:

Library for uploading test results to Xray Test Management

328 lines (327 loc) 13.3 kB
import { GraphQLClient, gql } from "graphql-request"; import axios from "axios"; import fs from "fs"; import FormData from "form-data"; import XrayErrorResponse from "./xray-error-response"; import XrayCloudResponseV2 from "./xray-cloud-response-v2"; // import XrayCloudGraphQLResponseV2 from './xray-cloud-graphql-response-v2'; import XrayCloudGraphQLErrorResponse from "./xray-cloud-graphql-error-response"; import { XRAY_FORMAT, JUNIT_FORMAT, TESTNG_FORMAT, ROBOT_FORMAT, NUNIT_FORMAT, XUNIT_FORMAT, CUCUMBER_FORMAT, BEHAVE_FORMAT, } from "./index"; const xrayCloudBaseUrl = "https://xray.cloud.getxray.app/api/v2"; const authenticateUrl = xrayCloudBaseUrl + "/authenticate"; export class XrayCloudClient { constructor(xraySettings) { this.supportedFormats = [ XRAY_FORMAT, JUNIT_FORMAT, TESTNG_FORMAT, ROBOT_FORMAT, NUNIT_FORMAT, XUNIT_FORMAT, CUCUMBER_FORMAT, BEHAVE_FORMAT, ]; this.clientId = xraySettings.clientId; this.clientSecret = xraySettings.clientSecret; if (xraySettings.timeout !== undefined) this.timeout = xraySettings.timeout; else this.timeout = 50000; axios.defaults.timeout = this.timeout; } async submitResults(reportPath, config) { if (config.format === undefined) throw new XrayErrorResponse("ERROR: format must be specified"); if (!this.supportedFormats.includes(config.format)) throw new XrayErrorResponse("ERROR: unsupported format " + config.format); let reportContent; try { reportContent = fs.readFileSync(reportPath).toString(); } catch (error) { throw new XrayErrorResponse(error.message); } // use a CancelToken as the timeout setting is not reliable const cancelTokenSource = axios.CancelToken.source(); const timeoutFn = setTimeout(() => { cancelTokenSource.cancel("request timeout"); }, this.timeout); return axios .post(authenticateUrl, { client_id: this.clientId, client_secret: this.clientSecret }, { timeout: this.timeout, cancelToken: cancelTokenSource.token, }) .then((response) => { const authToken = response.data; return authToken; }) .then((authToken) => { let endpointUrl; if (config.format === XRAY_FORMAT) { endpointUrl = xrayCloudBaseUrl + "/import/execution"; } else { endpointUrl = xrayCloudBaseUrl + "/import/execution/" + config.format; } const params = {}; let url = endpointUrl; // all formats support GET parameters, except for xray and cucumber if (![XRAY_FORMAT, CUCUMBER_FORMAT, BEHAVE_FORMAT].includes(config.format)) { if (config.projectKey === undefined && config.testExecKey === undefined) { throw new XrayErrorResponse("ERROR: projectKey or testExecKey must be defined"); } if (config.projectKey !== undefined) { params.projectKey = config.projectKey; } if (config.testPlanKey !== undefined) { params.testPlanKey = config.testPlanKey; } if (config.testExecKey !== undefined) { params.testExecKey = config.testExecKey; } if (config.version !== undefined) { params.fixVersion = config.version; } if (config.revision !== undefined) { params.revision = config.revision; } if (config.testEnvironment !== undefined) { params.testEnvironments = config.testEnvironment; } if (config.testEnvironments !== undefined) { params.testEnvironments = config.testEnvironments.join(";"); } const urlParams = new URLSearchParams({ ...params }).toString(); url = endpointUrl + "?" + urlParams; } let contentType; if ([ JUNIT_FORMAT, TESTNG_FORMAT, NUNIT_FORMAT, XUNIT_FORMAT, ROBOT_FORMAT, ].includes(config.format)) { contentType = "application/xml"; } else { contentType = "application/json"; } return axios.post(url, reportContent, { timeout: this.timeout, cancelToken: cancelTokenSource.token, headers: { Authorization: "Bearer " + authToken, "Content-Type": contentType, }, }); }) .then((response) => { clearTimeout(timeoutFn); return new XrayCloudResponseV2(response); }) .catch((error) => { if (error.response !== undefined) throw new XrayErrorResponse(error.response); else throw new XrayErrorResponse(error.message || error._response); }); } async submitResultsMultipart(reportPath, config) { if (config.format === undefined) throw new XrayErrorResponse("ERROR: format must be specified"); if (!this.supportedFormats.includes(config.format)) throw new XrayErrorResponse("ERROR: unsupported format " + config.format); if (config.testExecInfoFile === undefined && config.testExecInfo === undefined) throw new XrayErrorResponse("ERROR: testExecInfoFile or testExecInfo must be defined"); // use a CancelToken as the timeout setting is not reliable const cancelTokenSource = axios.CancelToken.source(); const timeoutFn = setTimeout(() => { cancelTokenSource.cancel("request timeout"); }, this.timeout); return axios .post(authenticateUrl, { client_id: this.clientId, client_secret: this.clientSecret }, { timeout: this.timeout, cancelToken: cancelTokenSource.token, }) .then((response) => { // clearTimeout(timeoutFn); const authToken = response.data; return authToken; }) .then((authToken) => { let endpointUrl; if (config.format === XRAY_FORMAT) { endpointUrl = xrayCloudBaseUrl + "/import/execution/multipart"; } else { endpointUrl = xrayCloudBaseUrl + "/import/execution/" + config.format + "/multipart"; } let reportContent; let testInfoContent; let testExecInfoContent; try { reportContent = fs.readFileSync(reportPath).toString(); if (config.testInfoFile !== undefined) testInfoContent = fs.readFileSync(config.testInfoFile).toString(); if (config.testInfo !== undefined) testInfoContent = config.testInfo.toString(); if (config.testExecInfoFile !== undefined) testExecInfoContent = fs .readFileSync(config.testExecInfoFile) .toString(); else testExecInfoContent = config.testExecInfo.toString(); } catch (error) { throw new XrayErrorResponse(error.message); } const bodyFormData = new FormData(); let fileName; if ([ JUNIT_FORMAT, TESTNG_FORMAT, NUNIT_FORMAT, XUNIT_FORMAT, ROBOT_FORMAT, ].includes(config.format)) { fileName = "report.xml"; } else { fileName = "report.json"; } bodyFormData.append("results", reportContent, fileName); bodyFormData.append("info", testExecInfoContent, "info.json"); if (testInfoContent !== undefined && [ JUNIT_FORMAT, TESTNG_FORMAT, NUNIT_FORMAT, XUNIT_FORMAT, ROBOT_FORMAT, ].includes(config.format)) bodyFormData.append("testInfo", testInfoContent, "testInfo.json"); return axios.post(endpointUrl, bodyFormData, { timeout: this.timeout, cancelToken: cancelTokenSource.token, headers: { Authorization: "Bearer " + authToken, ...bodyFormData.getHeaders(), }, }); }) .then((response) => { clearTimeout(timeoutFn); return new XrayCloudResponseV2(response); }) .catch((error) => { if (error.response !== undefined) throw new XrayErrorResponse(error.response); else throw new XrayErrorResponse(error.message || error._response); }); } async authenticate() { // use a CancelToken as the timeout setting is not reliable const cancelTokenSource = axios.CancelToken.source(); const timeoutFn = setTimeout(() => { cancelTokenSource.cancel("request timeout"); }, this.timeout); return axios .post(authenticateUrl, { client_id: this.clientId, client_secret: this.clientSecret }, { timeout: this.timeout, cancelToken: cancelTokenSource.token, }) .then((response) => { clearTimeout(timeoutFn); const authToken = response.data; return authToken; }); } async associateTestExecutionToTestPlanByIds(testExecIssueId, testPlanIssueId) { // use a CancelToken as the timeout setting is not reliable const cancelTokenSource = axios.CancelToken.source(); const timeoutFn = setTimeout(() => { cancelTokenSource.cancel("request timeout"); }, this.timeout); return axios .post(authenticateUrl, { client_id: this.clientId, client_secret: this.clientSecret }, { timeout: this.timeout, cancelToken: cancelTokenSource.token, }) .then((response) => { clearTimeout(timeoutFn); const authToken = response.data; return authToken; }) .then((authToken) => { const graphQLEndpoint = xrayCloudBaseUrl + "/graphql"; const graphQLClient = new GraphQLClient(graphQLEndpoint, { headers: { authorization: "Bearer " + authToken, }, }); const mutation = gql ` mutation { addTestExecutionsToTestPlan( issueId: "${testPlanIssueId}", testExecIssueIds: ["${testExecIssueId}"] ) { addedTestExecutions warning } } `; return graphQLClient.request(mutation); }) .then((response) => { return (response.data.addTestExecutionsToTestPlan.addedTestExecutions[0] || testExecIssueId); // return new XrayCloudGraphQLResponseV2(response, response.data.addTestExecutionsToTestPlan.addedTestExecutions[0] || testExecIssueId); }) .catch((error) => { const errorMessages = error.response.errors.map((err) => { return err.message; }); throw new XrayCloudGraphQLErrorResponse(error, errorMessages); }); } async getTestPlanId(testPlanIssueKey) { return this.authenticate() .then((authToken) => { const graphQLEndpoint = xrayCloudBaseUrl + "/graphql"; const graphQLClient = new GraphQLClient(graphQLEndpoint, { headers: { authorization: "Bearer " + authToken, }, }); const query = gql ` { getTestPlans(jql: "key = ${testPlanIssueKey}", limit: 1) { total results { issueId } } } `; return graphQLClient.request(query); }) .then((response) => { return response.getTestPlans.results[0].issueId; }) .catch((error) => { const errorMessages = error.response.errors.map((err) => { return err.message; }); throw new XrayCloudGraphQLErrorResponse(error, errorMessages); }); } } export default XrayCloudClient;