xray-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +259 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +851 -0
- package/dist/xray-client.d.ts +191 -0
- package/dist/xray-client.js +759 -0
- package/package.json +53 -0
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
export class XrayClient {
|
|
3
|
+
config;
|
|
4
|
+
client;
|
|
5
|
+
graphqlClient;
|
|
6
|
+
accessToken = null;
|
|
7
|
+
tokenExpiry = 0;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.config = {
|
|
10
|
+
...config,
|
|
11
|
+
baseUrl: config.baseUrl || 'https://xray.cloud.getxray.app/api/v2'
|
|
12
|
+
};
|
|
13
|
+
this.client = axios.create({
|
|
14
|
+
baseURL: this.config.baseUrl,
|
|
15
|
+
headers: {
|
|
16
|
+
'Content-Type': 'application/json'
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
this.graphqlClient = axios.create({
|
|
20
|
+
baseURL: this.config.baseUrl,
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json'
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Authenticate with Xray Cloud API and get access token
|
|
28
|
+
*/
|
|
29
|
+
async authenticate() {
|
|
30
|
+
// Check if we have a valid token
|
|
31
|
+
if (this.accessToken && Date.now() < this.tokenExpiry) {
|
|
32
|
+
return this.accessToken;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const response = await axios.post('https://xray.cloud.getxray.app/api/v1/authenticate', {
|
|
36
|
+
client_id: this.config.clientId,
|
|
37
|
+
client_secret: this.config.clientSecret
|
|
38
|
+
}, {
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json'
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
this.accessToken = response.data;
|
|
44
|
+
// Set expiry to 23 hours from now (tokens are valid for 24 hours)
|
|
45
|
+
this.tokenExpiry = Date.now() + (23 * 60 * 60 * 1000);
|
|
46
|
+
if (!this.accessToken) {
|
|
47
|
+
throw new Error('Authentication failed: No access token received');
|
|
48
|
+
}
|
|
49
|
+
return this.accessToken;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (axios.isAxiosError(error)) {
|
|
53
|
+
throw new Error(`Authentication failed: ${error.response?.data || error.message}`);
|
|
54
|
+
}
|
|
55
|
+
throw new Error(`Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Make an authenticated request to the Xray API
|
|
60
|
+
*/
|
|
61
|
+
async request(method, url, data) {
|
|
62
|
+
const token = await this.authenticate();
|
|
63
|
+
try {
|
|
64
|
+
const response = await this.client.request({
|
|
65
|
+
method,
|
|
66
|
+
url,
|
|
67
|
+
data,
|
|
68
|
+
headers: {
|
|
69
|
+
Authorization: `Bearer ${token}`
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
return response.data;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (axios.isAxiosError(error)) {
|
|
76
|
+
throw new Error(`API request failed: ${error.response?.data?.error || error.message}`);
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`API request failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Make a GraphQL query to Xray API
|
|
83
|
+
*/
|
|
84
|
+
async graphqlRequest(query, variables) {
|
|
85
|
+
const token = await this.authenticate();
|
|
86
|
+
try {
|
|
87
|
+
const response = await this.graphqlClient.post('/graphql', {
|
|
88
|
+
query,
|
|
89
|
+
variables
|
|
90
|
+
}, {
|
|
91
|
+
headers: {
|
|
92
|
+
Authorization: `Bearer ${token}`
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
if (response.data.errors && response.data.errors.length > 0) {
|
|
96
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(response.data.errors)}`);
|
|
97
|
+
}
|
|
98
|
+
return response.data.data;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (axios.isAxiosError(error)) {
|
|
102
|
+
const errorDetails = error.response?.data
|
|
103
|
+
? JSON.stringify(error.response.data)
|
|
104
|
+
: error.message;
|
|
105
|
+
throw new Error(`GraphQL request failed (${error.response?.status}): ${errorDetails}`);
|
|
106
|
+
}
|
|
107
|
+
throw new Error(`GraphQL request failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Create a new test case using GraphQL mutation
|
|
112
|
+
*/
|
|
113
|
+
async createTestCase(testCase) {
|
|
114
|
+
const mutation = `
|
|
115
|
+
mutation CreateTest($jira: JSON!, $testType: UpdateTestTypeInput, $unstructured: String) {
|
|
116
|
+
createTest(jira: $jira, testType: $testType, unstructured: $unstructured) {
|
|
117
|
+
test {
|
|
118
|
+
issueId
|
|
119
|
+
jira(fields: ["key"])
|
|
120
|
+
}
|
|
121
|
+
warnings
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
const jiraFields = {
|
|
126
|
+
fields: {
|
|
127
|
+
project: {
|
|
128
|
+
key: testCase.projectKey
|
|
129
|
+
},
|
|
130
|
+
summary: testCase.summary,
|
|
131
|
+
issuetype: {
|
|
132
|
+
name: 'Test'
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
if (testCase.description) {
|
|
137
|
+
jiraFields.fields.description = testCase.description;
|
|
138
|
+
}
|
|
139
|
+
if (testCase.labels && testCase.labels.length > 0) {
|
|
140
|
+
jiraFields.fields.labels = testCase.labels;
|
|
141
|
+
}
|
|
142
|
+
if (testCase.priority) {
|
|
143
|
+
jiraFields.fields.priority = { name: testCase.priority };
|
|
144
|
+
}
|
|
145
|
+
const variables = {
|
|
146
|
+
jira: jiraFields,
|
|
147
|
+
unstructured: testCase.description || ''
|
|
148
|
+
};
|
|
149
|
+
if (testCase.testType) {
|
|
150
|
+
variables.testType = {
|
|
151
|
+
name: testCase.testType
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const result = await this.graphqlRequest(mutation, variables);
|
|
155
|
+
return {
|
|
156
|
+
id: result.createTest.test.issueId,
|
|
157
|
+
key: result.createTest.test.jira.key,
|
|
158
|
+
self: `https://your-jira-instance.atlassian.net/browse/${result.createTest.test.jira.key}`
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get a test case by key using GraphQL
|
|
163
|
+
*/
|
|
164
|
+
async getTestCase(testKey) {
|
|
165
|
+
const query = `
|
|
166
|
+
query GetTest($jql: String!, $limit: Int!) {
|
|
167
|
+
getTests(jql: $jql, limit: $limit) {
|
|
168
|
+
total
|
|
169
|
+
results {
|
|
170
|
+
issueId
|
|
171
|
+
projectId
|
|
172
|
+
jira(fields: ["key", "summary", "description", "priority", "status", "labels"])
|
|
173
|
+
testType {
|
|
174
|
+
name
|
|
175
|
+
kind
|
|
176
|
+
}
|
|
177
|
+
steps {
|
|
178
|
+
id
|
|
179
|
+
action
|
|
180
|
+
data
|
|
181
|
+
result
|
|
182
|
+
}
|
|
183
|
+
gherkin
|
|
184
|
+
unstructured
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
`;
|
|
189
|
+
const variables = {
|
|
190
|
+
jql: `key = '${testKey}'`,
|
|
191
|
+
limit: 1
|
|
192
|
+
};
|
|
193
|
+
const result = await this.graphqlRequest(query, variables);
|
|
194
|
+
if (result.getTests.total === 0) {
|
|
195
|
+
throw new Error(`Test case ${testKey} not found`);
|
|
196
|
+
}
|
|
197
|
+
return result.getTests.results[0];
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Update a test case
|
|
201
|
+
* Note: Xray GraphQL API has specific mutations for test definition updates
|
|
202
|
+
* (updateUnstructuredTestDefinition, updateGherkinTestDefinition, etc.)
|
|
203
|
+
* For general Jira field updates (summary, description, labels), use Jira REST API directly
|
|
204
|
+
*/
|
|
205
|
+
async updateTestCase(testKey, updates) {
|
|
206
|
+
throw new Error('Direct test case update is not supported via Xray GraphQL API. ' +
|
|
207
|
+
'Use Jira REST API to update standard fields (summary, description, labels, priority). ' +
|
|
208
|
+
'Use specific Xray mutations for test definition updates: ' +
|
|
209
|
+
'updateUnstructuredTestDefinition, updateGherkinTestDefinition, updateTestType, etc.');
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Delete a test case using GraphQL mutation
|
|
213
|
+
* First retrieves the issueId from the test key, then deletes it
|
|
214
|
+
*/
|
|
215
|
+
async deleteTestCase(testKey) {
|
|
216
|
+
// First, get the issueId from the test key
|
|
217
|
+
const test = await this.getTestCase(testKey);
|
|
218
|
+
const mutation = `
|
|
219
|
+
mutation DeleteTest($issueId: String!) {
|
|
220
|
+
deleteTest(issueId: $issueId)
|
|
221
|
+
}
|
|
222
|
+
`;
|
|
223
|
+
const variables = {
|
|
224
|
+
issueId: test.issueId
|
|
225
|
+
};
|
|
226
|
+
await this.graphqlRequest(mutation, variables);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Get test cases for a project using GraphQL
|
|
230
|
+
*/
|
|
231
|
+
async getTestCasesByProject(projectKey, maxResults = 50) {
|
|
232
|
+
const jql = `project = '${projectKey}'`;
|
|
233
|
+
return this.searchTestCases(jql, maxResults);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Search test cases using JQL and GraphQL
|
|
237
|
+
*/
|
|
238
|
+
async searchTestCases(jql, maxResults = 50) {
|
|
239
|
+
const query = `
|
|
240
|
+
query SearchTests($jql: String!, $limit: Int!) {
|
|
241
|
+
getTests(jql: $jql, limit: $limit) {
|
|
242
|
+
total
|
|
243
|
+
start
|
|
244
|
+
limit
|
|
245
|
+
results {
|
|
246
|
+
issueId
|
|
247
|
+
projectId
|
|
248
|
+
jira(fields: ["key", "summary", "description", "priority", "status", "labels"])
|
|
249
|
+
testType {
|
|
250
|
+
name
|
|
251
|
+
kind
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
`;
|
|
257
|
+
const variables = {
|
|
258
|
+
jql,
|
|
259
|
+
limit: maxResults
|
|
260
|
+
};
|
|
261
|
+
const result = await this.graphqlRequest(query, variables);
|
|
262
|
+
return result.getTests;
|
|
263
|
+
}
|
|
264
|
+
// ========================================
|
|
265
|
+
// Test Execution Methods
|
|
266
|
+
// ========================================
|
|
267
|
+
/**
|
|
268
|
+
* Create a new test execution using GraphQL mutation
|
|
269
|
+
*/
|
|
270
|
+
async createTestExecution(testExecution) {
|
|
271
|
+
const mutation = `
|
|
272
|
+
mutation CreateTestExecution($jira: JSON!, $testIssueIds: [String], $testEnvironments: [String]) {
|
|
273
|
+
createTestExecution(jira: $jira, testIssueIds: $testIssueIds, testEnvironments: $testEnvironments) {
|
|
274
|
+
testExecution {
|
|
275
|
+
issueId
|
|
276
|
+
jira(fields: ["key", "summary"])
|
|
277
|
+
testRuns(limit: 100) {
|
|
278
|
+
results {
|
|
279
|
+
id
|
|
280
|
+
status {
|
|
281
|
+
name
|
|
282
|
+
description
|
|
283
|
+
}
|
|
284
|
+
test {
|
|
285
|
+
issueId
|
|
286
|
+
jira(fields: ["key", "summary"])
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
warnings
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
`;
|
|
295
|
+
const jiraFields = {
|
|
296
|
+
fields: {
|
|
297
|
+
project: {
|
|
298
|
+
key: testExecution.projectKey
|
|
299
|
+
},
|
|
300
|
+
summary: testExecution.summary,
|
|
301
|
+
issuetype: {
|
|
302
|
+
name: 'Test Execution'
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
if (testExecution.description) {
|
|
307
|
+
jiraFields.fields.description = testExecution.description;
|
|
308
|
+
}
|
|
309
|
+
const variables = {
|
|
310
|
+
jira: jiraFields
|
|
311
|
+
};
|
|
312
|
+
if (testExecution.testIssueIds && testExecution.testIssueIds.length > 0) {
|
|
313
|
+
variables.testIssueIds = testExecution.testIssueIds;
|
|
314
|
+
}
|
|
315
|
+
if (testExecution.testEnvironments && testExecution.testEnvironments.length > 0) {
|
|
316
|
+
variables.testEnvironments = testExecution.testEnvironments;
|
|
317
|
+
}
|
|
318
|
+
const result = await this.graphqlRequest(mutation, variables);
|
|
319
|
+
return {
|
|
320
|
+
issueId: result.createTestExecution.testExecution.issueId,
|
|
321
|
+
key: result.createTestExecution.testExecution.jira.key,
|
|
322
|
+
testRuns: result.createTestExecution.testExecution.testRuns.results
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get a test execution by key using GraphQL
|
|
327
|
+
*/
|
|
328
|
+
async getTestExecution(testExecutionKey) {
|
|
329
|
+
const query = `
|
|
330
|
+
query GetTestExecution($jql: String!, $limit: Int!) {
|
|
331
|
+
getTestExecutions(jql: $jql, limit: $limit) {
|
|
332
|
+
total
|
|
333
|
+
results {
|
|
334
|
+
issueId
|
|
335
|
+
projectId
|
|
336
|
+
jira(fields: ["key", "summary", "description", "status"])
|
|
337
|
+
testRuns(limit: 100) {
|
|
338
|
+
results {
|
|
339
|
+
id
|
|
340
|
+
status {
|
|
341
|
+
name
|
|
342
|
+
description
|
|
343
|
+
}
|
|
344
|
+
test {
|
|
345
|
+
issueId
|
|
346
|
+
jira(fields: ["key", "summary"])
|
|
347
|
+
}
|
|
348
|
+
startedOn
|
|
349
|
+
finishedOn
|
|
350
|
+
executedBy
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
`;
|
|
357
|
+
const variables = {
|
|
358
|
+
jql: `key = '${testExecutionKey}'`,
|
|
359
|
+
limit: 1
|
|
360
|
+
};
|
|
361
|
+
const result = await this.graphqlRequest(query, variables);
|
|
362
|
+
if (result.getTestExecutions.total === 0) {
|
|
363
|
+
throw new Error(`Test execution ${testExecutionKey} not found`);
|
|
364
|
+
}
|
|
365
|
+
return result.getTestExecutions.results[0];
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Get test executions for a project using GraphQL
|
|
369
|
+
*/
|
|
370
|
+
async getTestExecutionsByProject(projectKey, maxResults = 50) {
|
|
371
|
+
const jql = `project = '${projectKey}'`;
|
|
372
|
+
return this.searchTestExecutions(jql, maxResults);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Search test executions using JQL and GraphQL
|
|
376
|
+
*/
|
|
377
|
+
async searchTestExecutions(jql, maxResults = 50) {
|
|
378
|
+
const query = `
|
|
379
|
+
query SearchTestExecutions($jql: String!, $limit: Int!) {
|
|
380
|
+
getTestExecutions(jql: $jql, limit: $limit) {
|
|
381
|
+
total
|
|
382
|
+
start
|
|
383
|
+
limit
|
|
384
|
+
results {
|
|
385
|
+
issueId
|
|
386
|
+
projectId
|
|
387
|
+
jira(fields: ["key", "summary", "description", "status", "created", "updated"])
|
|
388
|
+
testRuns(limit: 100) {
|
|
389
|
+
total
|
|
390
|
+
results {
|
|
391
|
+
id
|
|
392
|
+
status {
|
|
393
|
+
name
|
|
394
|
+
description
|
|
395
|
+
}
|
|
396
|
+
test {
|
|
397
|
+
issueId
|
|
398
|
+
jira(fields: ["key", "summary"])
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
`;
|
|
406
|
+
const variables = {
|
|
407
|
+
jql,
|
|
408
|
+
limit: maxResults
|
|
409
|
+
};
|
|
410
|
+
const result = await this.graphqlRequest(query, variables);
|
|
411
|
+
return result.getTestExecutions;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Update the status of a test run using GraphQL mutation
|
|
415
|
+
*/
|
|
416
|
+
async updateTestRunStatus(testRunId, status) {
|
|
417
|
+
const mutation = `
|
|
418
|
+
mutation UpdateTestRunStatus($id: String!, $status: String!) {
|
|
419
|
+
updateTestRunStatus(id: $id, status: $status)
|
|
420
|
+
}
|
|
421
|
+
`;
|
|
422
|
+
const variables = {
|
|
423
|
+
id: testRunId,
|
|
424
|
+
status
|
|
425
|
+
};
|
|
426
|
+
const result = await this.graphqlRequest(mutation, variables);
|
|
427
|
+
return result.updateTestRunStatus;
|
|
428
|
+
}
|
|
429
|
+
// ========================================
|
|
430
|
+
// Test Plan Methods
|
|
431
|
+
// ========================================
|
|
432
|
+
/**
|
|
433
|
+
* Create a new test plan using GraphQL mutation
|
|
434
|
+
*/
|
|
435
|
+
async createTestPlan(testPlan) {
|
|
436
|
+
const mutation = `
|
|
437
|
+
mutation CreateTestPlan($jira: JSON!, $testIssueIds: [String]) {
|
|
438
|
+
createTestPlan(jira: $jira, testIssueIds: $testIssueIds) {
|
|
439
|
+
testPlan {
|
|
440
|
+
issueId
|
|
441
|
+
jira(fields: ["key", "summary"])
|
|
442
|
+
tests(limit: 100) {
|
|
443
|
+
results {
|
|
444
|
+
issueId
|
|
445
|
+
jira(fields: ["key", "summary"])
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
warnings
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
`;
|
|
453
|
+
const jiraFields = {
|
|
454
|
+
fields: {
|
|
455
|
+
project: {
|
|
456
|
+
key: testPlan.projectKey
|
|
457
|
+
},
|
|
458
|
+
summary: testPlan.summary,
|
|
459
|
+
issuetype: {
|
|
460
|
+
name: 'Test Plan'
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
if (testPlan.description) {
|
|
465
|
+
jiraFields.fields.description = testPlan.description;
|
|
466
|
+
}
|
|
467
|
+
const variables = {
|
|
468
|
+
jira: jiraFields
|
|
469
|
+
};
|
|
470
|
+
if (testPlan.testIssueIds && testPlan.testIssueIds.length > 0) {
|
|
471
|
+
variables.testIssueIds = testPlan.testIssueIds;
|
|
472
|
+
}
|
|
473
|
+
const result = await this.graphqlRequest(mutation, variables);
|
|
474
|
+
return {
|
|
475
|
+
issueId: result.createTestPlan.testPlan.issueId,
|
|
476
|
+
key: result.createTestPlan.testPlan.jira.key,
|
|
477
|
+
tests: result.createTestPlan.testPlan.tests?.results || []
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Get a test plan by key using GraphQL
|
|
482
|
+
*/
|
|
483
|
+
async getTestPlan(testPlanKey) {
|
|
484
|
+
const query = `
|
|
485
|
+
query GetTestPlan($jql: String!, $limit: Int!) {
|
|
486
|
+
getTestPlans(jql: $jql, limit: $limit) {
|
|
487
|
+
total
|
|
488
|
+
results {
|
|
489
|
+
issueId
|
|
490
|
+
projectId
|
|
491
|
+
jira(fields: ["key", "summary", "description", "status"])
|
|
492
|
+
tests(limit: 100) {
|
|
493
|
+
total
|
|
494
|
+
results {
|
|
495
|
+
issueId
|
|
496
|
+
jira(fields: ["key", "summary", "status"])
|
|
497
|
+
testType {
|
|
498
|
+
name
|
|
499
|
+
kind
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
`;
|
|
507
|
+
const variables = {
|
|
508
|
+
jql: `key = '${testPlanKey}'`,
|
|
509
|
+
limit: 1
|
|
510
|
+
};
|
|
511
|
+
const result = await this.graphqlRequest(query, variables);
|
|
512
|
+
if (result.getTestPlans.total === 0) {
|
|
513
|
+
throw new Error(`Test plan ${testPlanKey} not found`);
|
|
514
|
+
}
|
|
515
|
+
return result.getTestPlans.results[0];
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Search test plans using JQL and GraphQL
|
|
519
|
+
*/
|
|
520
|
+
async searchTestPlans(jql, maxResults = 50) {
|
|
521
|
+
const query = `
|
|
522
|
+
query SearchTestPlans($jql: String!, $limit: Int!) {
|
|
523
|
+
getTestPlans(jql: $jql, limit: $limit) {
|
|
524
|
+
total
|
|
525
|
+
start
|
|
526
|
+
limit
|
|
527
|
+
results {
|
|
528
|
+
issueId
|
|
529
|
+
projectId
|
|
530
|
+
jira(fields: ["key", "summary", "description", "status", "created", "updated"])
|
|
531
|
+
tests(limit: 10) {
|
|
532
|
+
total
|
|
533
|
+
results {
|
|
534
|
+
issueId
|
|
535
|
+
jira(fields: ["key", "summary"])
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
`;
|
|
542
|
+
const variables = {
|
|
543
|
+
jql,
|
|
544
|
+
limit: maxResults
|
|
545
|
+
};
|
|
546
|
+
const result = await this.graphqlRequest(query, variables);
|
|
547
|
+
return result.getTestPlans;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Get test plans for a project using GraphQL
|
|
551
|
+
*/
|
|
552
|
+
async getTestPlansByProject(projectKey, maxResults = 50) {
|
|
553
|
+
const jql = `project = '${projectKey}'`;
|
|
554
|
+
return this.searchTestPlans(jql, maxResults);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Add tests to a test plan using GraphQL mutation
|
|
558
|
+
*/
|
|
559
|
+
async addTestsToTestPlan(testPlanIssueId, testIssueIds) {
|
|
560
|
+
const mutation = `
|
|
561
|
+
mutation AddTestsToTestPlan($issueId: String!, $testIssueIds: [String]!) {
|
|
562
|
+
addTestsToTestPlan(issueId: $issueId, testIssueIds: $testIssueIds) {
|
|
563
|
+
addedTests
|
|
564
|
+
warning
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
`;
|
|
568
|
+
const variables = {
|
|
569
|
+
issueId: testPlanIssueId,
|
|
570
|
+
testIssueIds
|
|
571
|
+
};
|
|
572
|
+
const result = await this.graphqlRequest(mutation, variables);
|
|
573
|
+
return result.addTestsToTestPlan;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Remove tests from a test plan using GraphQL mutation
|
|
577
|
+
*/
|
|
578
|
+
async removeTestsFromTestPlan(testPlanIssueId, testIssueIds) {
|
|
579
|
+
const mutation = `
|
|
580
|
+
mutation RemoveTestsFromTestPlan($issueId: String!, $testIssueIds: [String]!) {
|
|
581
|
+
removeTestsFromTestPlan(issueId: $issueId, testIssueIds: $testIssueIds) {
|
|
582
|
+
removedTests
|
|
583
|
+
warning
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
`;
|
|
587
|
+
const variables = {
|
|
588
|
+
issueId: testPlanIssueId,
|
|
589
|
+
testIssueIds
|
|
590
|
+
};
|
|
591
|
+
const result = await this.graphqlRequest(mutation, variables);
|
|
592
|
+
return result.removeTestsFromTestPlan;
|
|
593
|
+
}
|
|
594
|
+
// ========================================
|
|
595
|
+
// Test Set Methods
|
|
596
|
+
// ========================================
|
|
597
|
+
/**
|
|
598
|
+
* Create a new test set using GraphQL mutation
|
|
599
|
+
*/
|
|
600
|
+
async createTestSet(testSet) {
|
|
601
|
+
const mutation = `
|
|
602
|
+
mutation CreateTestSet($jira: JSON!, $testIssueIds: [String]) {
|
|
603
|
+
createTestSet(jira: $jira, testIssueIds: $testIssueIds) {
|
|
604
|
+
testSet {
|
|
605
|
+
issueId
|
|
606
|
+
jira(fields: ["key", "summary"])
|
|
607
|
+
tests(limit: 100) {
|
|
608
|
+
results {
|
|
609
|
+
issueId
|
|
610
|
+
jira(fields: ["key", "summary"])
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
warnings
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
`;
|
|
618
|
+
const jiraFields = {
|
|
619
|
+
fields: {
|
|
620
|
+
project: {
|
|
621
|
+
key: testSet.projectKey
|
|
622
|
+
},
|
|
623
|
+
summary: testSet.summary,
|
|
624
|
+
issuetype: {
|
|
625
|
+
name: 'Test Set'
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
if (testSet.description) {
|
|
630
|
+
jiraFields.fields.description = testSet.description;
|
|
631
|
+
}
|
|
632
|
+
const variables = {
|
|
633
|
+
jira: jiraFields
|
|
634
|
+
};
|
|
635
|
+
if (testSet.testIssueIds && testSet.testIssueIds.length > 0) {
|
|
636
|
+
variables.testIssueIds = testSet.testIssueIds;
|
|
637
|
+
}
|
|
638
|
+
const result = await this.graphqlRequest(mutation, variables);
|
|
639
|
+
return {
|
|
640
|
+
issueId: result.createTestSet.testSet.issueId,
|
|
641
|
+
key: result.createTestSet.testSet.jira.key,
|
|
642
|
+
tests: result.createTestSet.testSet.tests?.results || []
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Get a test set by key using GraphQL
|
|
647
|
+
*/
|
|
648
|
+
async getTestSet(testSetKey) {
|
|
649
|
+
const query = `
|
|
650
|
+
query GetTestSet($jql: String!, $limit: Int!) {
|
|
651
|
+
getTestSets(jql: $jql, limit: $limit) {
|
|
652
|
+
total
|
|
653
|
+
results {
|
|
654
|
+
issueId
|
|
655
|
+
projectId
|
|
656
|
+
jira(fields: ["key", "summary", "description", "status"])
|
|
657
|
+
tests(limit: 100) {
|
|
658
|
+
total
|
|
659
|
+
results {
|
|
660
|
+
issueId
|
|
661
|
+
jira(fields: ["key", "summary", "status"])
|
|
662
|
+
testType {
|
|
663
|
+
name
|
|
664
|
+
kind
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
`;
|
|
672
|
+
const variables = {
|
|
673
|
+
jql: `key = '${testSetKey}'`,
|
|
674
|
+
limit: 1
|
|
675
|
+
};
|
|
676
|
+
const result = await this.graphqlRequest(query, variables);
|
|
677
|
+
if (result.getTestSets.total === 0) {
|
|
678
|
+
throw new Error(`Test set ${testSetKey} not found`);
|
|
679
|
+
}
|
|
680
|
+
return result.getTestSets.results[0];
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Search test sets using JQL and GraphQL
|
|
684
|
+
*/
|
|
685
|
+
async searchTestSets(jql, maxResults = 50) {
|
|
686
|
+
const query = `
|
|
687
|
+
query SearchTestSets($jql: String!, $limit: Int!) {
|
|
688
|
+
getTestSets(jql: $jql, limit: $limit) {
|
|
689
|
+
total
|
|
690
|
+
start
|
|
691
|
+
limit
|
|
692
|
+
results {
|
|
693
|
+
issueId
|
|
694
|
+
projectId
|
|
695
|
+
jira(fields: ["key", "summary", "description", "status", "created", "updated"])
|
|
696
|
+
tests(limit: 10) {
|
|
697
|
+
total
|
|
698
|
+
results {
|
|
699
|
+
issueId
|
|
700
|
+
jira(fields: ["key", "summary"])
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
`;
|
|
707
|
+
const variables = {
|
|
708
|
+
jql,
|
|
709
|
+
limit: maxResults
|
|
710
|
+
};
|
|
711
|
+
const result = await this.graphqlRequest(query, variables);
|
|
712
|
+
return result.getTestSets;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Get test sets for a project using GraphQL
|
|
716
|
+
*/
|
|
717
|
+
async getTestSetsByProject(projectKey, maxResults = 50) {
|
|
718
|
+
const jql = `project = '${projectKey}'`;
|
|
719
|
+
return this.searchTestSets(jql, maxResults);
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Add tests to a test set using GraphQL mutation
|
|
723
|
+
*/
|
|
724
|
+
async addTestsToTestSet(testSetIssueId, testIssueIds) {
|
|
725
|
+
const mutation = `
|
|
726
|
+
mutation AddTestsToTestSet($issueId: String!, $testIssueIds: [String]!) {
|
|
727
|
+
addTestsToTestSet(issueId: $issueId, testIssueIds: $testIssueIds) {
|
|
728
|
+
addedTests
|
|
729
|
+
warning
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
`;
|
|
733
|
+
const variables = {
|
|
734
|
+
issueId: testSetIssueId,
|
|
735
|
+
testIssueIds
|
|
736
|
+
};
|
|
737
|
+
const result = await this.graphqlRequest(mutation, variables);
|
|
738
|
+
return result.addTestsToTestSet;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Remove tests from a test set using GraphQL mutation
|
|
742
|
+
*/
|
|
743
|
+
async removeTestsFromTestSet(testSetIssueId, testIssueIds) {
|
|
744
|
+
const mutation = `
|
|
745
|
+
mutation RemoveTestsFromTestSet($issueId: String!, $testIssueIds: [String]!) {
|
|
746
|
+
removeTestsFromTestSet(issueId: $issueId, testIssueIds: $testIssueIds) {
|
|
747
|
+
removedTests
|
|
748
|
+
warning
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
`;
|
|
752
|
+
const variables = {
|
|
753
|
+
issueId: testSetIssueId,
|
|
754
|
+
testIssueIds
|
|
755
|
+
};
|
|
756
|
+
const result = await this.graphqlRequest(mutation, variables);
|
|
757
|
+
return result.removeTestsFromTestSet;
|
|
758
|
+
}
|
|
759
|
+
}
|