Skip to content

Commit abd0616

Browse files
author
Fúlvio Carvalhido
committed
Add working cloud code and start with server code. Needs to be tested.
1 parent af33a7a commit abd0616

21 files changed

+1465
-10910
lines changed

README.md

+3-18
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,9 @@ import { PlaywrightTestConfig } from '@playwright/test';
2323

2424
const config: PlaywrightTestConfig = {
2525
reporter: [['playwright-xray', {
26-
host: 'https://jira.your-company-domain.com/',
27-
user: 'username',
28-
password: 'password',
29-
projectKey: 'JARV'
30-
}]],
31-
}
32-
```
33-
34-
With `authorizationToken` option instead of `user` and `password`:
35-
36-
```typescript
37-
// playwright.config.ts
38-
import { PlaywrightTestConfig } from '@playwright/test';
39-
40-
const config: PlaywrightTestConfig = {
41-
reporter: [['playwright-xray', {
42-
host: 'https://jira.your-company-domain.com/',
43-
authorizationToken: 'SVSdrtwgDSA312342--',
26+
host: 'https://xray.cloud.getxray.app/api/v2',
27+
client_id: 'client_id',
28+
client_secret: 'password',
4429
projectKey: 'JARV'
4530
}]],
4631
}

lib/src/index.d.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import type { ZephyrOptions } from '../types/zephyr.types';
1+
import type { XrayOptions } from '../types/xray.types';
22
import type { Reporter, TestCase, TestResult } from '@playwright/test/reporter';
3-
declare class ZephyrReporter implements Reporter {
4-
private zephyrService;
3+
declare class XrayReporter implements Reporter {
4+
private xrayService;
55
private testResults;
6-
private projectKey;
76
private testCaseKeyPattern;
87
private options;
9-
constructor(options: ZephyrOptions);
8+
private totalDuration;
9+
private readonly defaultRunName;
10+
constructor(options: XrayOptions);
1011
onBegin(): Promise<void>;
11-
onTestEnd(test: TestCase, result: TestResult): void;
12+
onTestEnd(testCase: TestCase, result: TestResult): Promise<void>;
1213
onEnd(): Promise<void>;
1314
}
14-
export default ZephyrReporter;
15+
export default XrayReporter;

lib/src/index.js

+49-35
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,65 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3-
const zephyr_service_1 = require("./zephyr.service");
4-
function convertPwStatusToZephyr(status) {
5-
if (status === 'passed')
6-
return 'Pass';
7-
if (status === 'failed')
8-
return 'Fail';
9-
if (status === 'skipped')
10-
return 'Not Executed';
11-
if (status === 'timedOut')
12-
return 'Blocked';
13-
return 'Not Executed';
14-
}
15-
class ZephyrReporter {
3+
const xray_service_1 = require("./xray.service");
4+
class XrayReporter {
165
constructor(options) {
17-
this.testResults = [];
186
this.testCaseKeyPattern = /\[(.*?)\]/;
7+
this.defaultRunName = `[${new Date().toUTCString()}] - Automated run`;
198
this.options = options;
9+
this.xrayService = new xray_service_1.XrayService(this.options);
10+
this.totalDuration = 0;
11+
//const finishTime = new Date(this.xrayService.startTime.getTime() + (result.duration * 1000));
12+
const testResults = {
13+
info: {
14+
summary: this.defaultRunName,
15+
startDate: new Date().toISOString(),
16+
finishDate: new Date().toISOString(),
17+
testPlanKey: this.options.testPlan,
18+
revision: '2536',
19+
},
20+
tests: [],
21+
};
22+
this.testResults = testResults;
2023
}
21-
async onBegin() {
22-
this.projectKey = this.options.projectKey;
23-
this.zephyrService = new zephyr_service_1.ZephyrService(this.options);
24-
}
25-
onTestEnd(test, result) {
26-
if (test.title.match(this.testCaseKeyPattern) && test.title.match(this.testCaseKeyPattern).length > 1) {
27-
const [, projectName] = test.titlePath();
28-
const [, testCaseId] = test.title.match(this.testCaseKeyPattern);
29-
const testCaseKey = `${this.projectKey}-${testCaseId}`;
30-
const status = convertPwStatusToZephyr(result.status);
24+
async onBegin() { }
25+
async onTestEnd(testCase, result) {
26+
const testCaseId = testCase.title.match(this.testCaseKeyPattern);
27+
const testCode = testCaseId != null ? testCaseId[1] : '';
28+
if (testCode != '') {
3129
// @ts-ignore
32-
const browserName = test._pool.registrations.get('browserName').fn;
33-
const capitalize = (word) => word && word[0].toUpperCase() + word.slice(1);
34-
this.testResults.push({
35-
testCaseKey,
36-
status,
37-
environment: projectName || capitalize(browserName),
38-
executionDate: new Date().toISOString(),
39-
});
30+
const browserName = testCase._pool.registrations.get('browserName').fn;
31+
const finishTime = new Date(result.startTime.getTime() + result.duration * 1000);
32+
this.totalDuration = this.totalDuration + result.duration;
33+
let xrayTestData = {
34+
testKey: testCode,
35+
status: result.status.toUpperCase(),
36+
start: result.startTime.toISOString(),
37+
finish: finishTime.toISOString(),
38+
steps: [],
39+
};
40+
// Generated step and error messages
41+
await Promise.all(result.steps.map(async (step) => {
42+
if (step.category != 'hook') {
43+
const xrayTestStep = {
44+
status: typeof step.error == 'object' ? 'FAILED' : 'SUCCESS',
45+
comment: step.title,
46+
actualResult: typeof step.error == 'object' ? step.error.message?.toString() : '',
47+
};
48+
xrayTestData.steps.push(xrayTestStep);
49+
}
50+
}));
51+
this.testResults.tests.push(xrayTestData);
4052
}
4153
}
4254
async onEnd() {
43-
if (this.testResults.length > 0) {
44-
await this.zephyrService.createRun(this.testResults);
55+
// Update test Duration
56+
this.testResults.info.finishDate = new Date(new Date(this.testResults.info.startDate).getTime() + this.totalDuration).toISOString();
57+
if (typeof this.testResults != 'undefined' && typeof this.testResults.tests != 'undefined' && this.testResults.tests.length > 0) {
58+
await this.xrayService.createRun(this.testResults);
4559
}
4660
else {
4761
console.log(`There are no tests with such ${this.testCaseKeyPattern} key pattern`);
4862
}
4963
}
5064
}
51-
exports.default = ZephyrReporter;
65+
exports.default = XrayReporter;

lib/src/xray.service.d.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { XrayOptions } from '../types/xray.types';
2+
import { XrayTestResult } from '../types/cloud.types';
3+
export declare class XrayService {
4+
private readonly xray;
5+
private readonly jira;
6+
private readonly username;
7+
private readonly password;
8+
private readonly type;
9+
private readonly requestUrl;
10+
private axios;
11+
private readonly xrayOptions;
12+
constructor(options: XrayOptions);
13+
createRun(results: XrayTestResult): Promise<void>;
14+
}

lib/src/xray.service.js

+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"use strict";
2+
var __importDefault = (this && this.__importDefault) || function (mod) {
3+
return (mod && mod.__esModule) ? mod : { "default": mod };
4+
};
5+
Object.defineProperty(exports, "__esModule", { value: true });
6+
const axios_1 = __importDefault(require("axios"));
7+
const util_1 = require("util");
8+
const picocolors_1 = require("picocolors");
9+
function isAxiosError(error) {
10+
return error.isAxiosError === true;
11+
}
12+
class XrayService {
13+
constructor(options) {
14+
// Init vars
15+
this.xray = "";
16+
this.username = "";
17+
this.password = "";
18+
this.requestUrl = "";
19+
// Set Jira URL
20+
if (!options.jira.url)
21+
throw new Error('"jira.url" option is missed. Please, provide it in the config');
22+
this.jira = options.jira.url;
23+
// Set Jira Server Type
24+
if (!options.jira.type)
25+
throw new Error('"jira.type" option is missed. Please, provide it in the config');
26+
this.type = options.jira.type;
27+
// Init axios instance
28+
this.axios = axios_1.default;
29+
switch (this.type) {
30+
case 'cloud':
31+
// Set Xray Server URL
32+
this.xray = 'https://xray.cloud.getxray.app/';
33+
// Set Xray Credencials
34+
if (!options.cloud?.client_id || !options.cloud?.client_secret)
35+
throw new Error('"cloud.client_id" and/or "cloud.client_secret" options are missed. Please provide them in the config');
36+
this.username = options.cloud?.client_id;
37+
this.password = options.cloud?.client_secret;
38+
// Set Request URL
39+
this.requestUrl = this.xray + 'api/v2';
40+
//Create Axios Instance with Auth
41+
axios_1.default
42+
.post(this.requestUrl + '/authenticate', {
43+
client_id: this.username,
44+
client_secret: this.password,
45+
})
46+
.then((request) => {
47+
this.axios = axios_1.default.create({
48+
baseURL: this.xray,
49+
headers: {
50+
'Content-Type': 'application/json',
51+
Authorization: `Bearer ${request.data}`,
52+
},
53+
});
54+
})
55+
.catch((error) => {
56+
throw new Error(`Failed to autenticate do host ${this.xray} with error: ${error}`);
57+
});
58+
break;
59+
case 'server':
60+
// Set Xray Server URL
61+
if (!options.server?.url)
62+
throw new Error('"host" option is missed. Please, provide it in the config');
63+
this.xray = options.server?.url;
64+
// Set Xray Credencials
65+
if (!options.server?.username || !options.server?.password)
66+
throw new Error('"server.username" and/or "server.password" options are missed. Please provide them in the config');
67+
this.username = options.server?.username;
68+
this.password = options.server?.password;
69+
// Set Request URL
70+
this.requestUrl = this.xray + 'rest/raven/2.0/api';
71+
//Create Axios Instance with Auth
72+
const token = `${this.username}:${this.password}`;
73+
const encodedToken = Buffer.from(token).toString('base64');
74+
this.axios = axios_1.default.create({
75+
baseURL: this.xray,
76+
headers: {
77+
'Content-Type': 'application/json',
78+
Authorization: `Basic ${encodedToken}`,
79+
},
80+
});
81+
break;
82+
}
83+
// Set Project Key
84+
if (!options.projectKey)
85+
throw new Error('"projectKey" option is missed. Please, provide it in the config');
86+
// Set Test Plan
87+
if (!options.testPlan)
88+
throw new Error('"testPlan" option are missed. Please provide them in the config');
89+
this.xrayOptions = options;
90+
}
91+
async createRun(results) {
92+
const URL = `${this.requestUrl}/import/execution`;
93+
try {
94+
const response = await this.axios.post(URL, JSON.stringify(results));
95+
if (response.status !== 200)
96+
throw new Error(`${response.status} - Failed to create test cycle`);
97+
const { data: { key, id }, } = response;
98+
console.log(`${picocolors_1.bold(picocolors_1.green(`✅ Test cycle ${key} has been created`))}`);
99+
console.log(`${picocolors_1.bold(picocolors_1.green('👇 Check out the test result'))}`);
100+
console.log(`${picocolors_1.bold(picocolors_1.green(`🔗 ${this.jira}/browse/${id}`))}`);
101+
}
102+
catch (error) {
103+
if (isAxiosError(error)) {
104+
console.error(`Config: ${util_1.inspect(error.config)}`);
105+
if (error.response) {
106+
throw new Error(`\nStatus: ${error.response.status} \nHeaders: ${util_1.inspect(error.response.headers)} \nData: ${util_1.inspect(error.response.data)}`);
107+
}
108+
else if (error.request) {
109+
throw new Error(`The request was made but no response was received. \n Error: ${util_1.inspect(error.toJSON())}`);
110+
}
111+
else {
112+
throw new Error(`Something happened in setting up the request that triggered an Error\n : ${util_1.inspect(error.message)}`);
113+
}
114+
}
115+
throw new Error(`\nUnknown error: ${error}`);
116+
}
117+
}
118+
}
119+
exports.XrayService = XrayService;

lib/types/cloud.types.d.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export declare type XrayStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';
2+
export interface XrayTestResult {
3+
testExecutionKey?: string;
4+
info: XrayInfo;
5+
tests?: XrayTest[];
6+
}
7+
export interface XrayInfo {
8+
summary: string;
9+
description?: string;
10+
version?: string;
11+
user?: string;
12+
revision?: string;
13+
startDate: string;
14+
finishDate: string;
15+
testPlanKey: string;
16+
testEnvironments?: object;
17+
}
18+
export interface XrayTest {
19+
testKey: string;
20+
start: string;
21+
finish: string;
22+
actualResult?: string;
23+
status: string;
24+
evidence?: XrayTestEvidence[];
25+
steps?: XrayTestSteps[];
26+
defects?: object;
27+
}
28+
export interface XrayTestSteps {
29+
status: string;
30+
comment?: string;
31+
actualResult?: string;
32+
evidences?: XrayTestEvidence[];
33+
}
34+
export interface XrayTestEvidence {
35+
data: string;
36+
filename: string;
37+
contentType: string;
38+
}

lib/types/cloud.types.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
;
4+
;
5+
;
6+
;
7+
;

lib/types/xray.types.d.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export interface XrayOptions {
2+
jira: {
3+
url: string;
4+
type: string;
5+
};
6+
cloud?: {
7+
client_id?: string;
8+
client_secret?: string;
9+
};
10+
server?: {
11+
url: string;
12+
username: string;
13+
password: string;
14+
};
15+
projectKey: string;
16+
testPlan: string;
17+
}

lib/types/xray.types.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });

0 commit comments

Comments
 (0)