@allurereport/plugin-testops 3.1.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/README.md +72 -0
- package/dist/client.d.ts +20 -0
- package/dist/client.js +179 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/model.d.ts +34 -0
- package/dist/model.js +1 -0
- package/dist/plugin.d.ts +12 -0
- package/dist/plugin.js +163 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.js +34 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Allure TestOps Plugin
|
|
2
|
+
|
|
3
|
+
[<img src="https://allurereport.org/public/img/allure-report.svg" height="85px" alt="Allure Report logo" align="right" />](https://allurereport.org "Allure Report")
|
|
4
|
+
|
|
5
|
+
- Learn more about Allure Report at https://allurereport.org
|
|
6
|
+
- 📚 [Documentation](https://allurereport.org/docs/) – discover official documentation for Allure Report
|
|
7
|
+
- ❓ [Questions and Support](https://github.com/orgs/allure-framework/discussions/categories/questions-support) – get help from the team and community
|
|
8
|
+
- 📢 [Official announcements](https://github.com/orgs/allure-framework/discussions/categories/announcements) – be in touch with the latest updates
|
|
9
|
+
- 💬 [General Discussion ](https://github.com/orgs/allure-framework/discussions/categories/general-discussion) – engage in casual conversations, share insights and ideas with the community
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
The plugin creates a new launch in Allure TestOps with all the tests data from the current report.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
Use your favorite package manager to install the package:
|
|
20
|
+
|
|
21
|
+
```shell
|
|
22
|
+
npm add @allurereport/plugin-testops
|
|
23
|
+
yarn add @allurereport/plugin-testops
|
|
24
|
+
pnpm add @allurereport/plugin-testops
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then, add the plugin to the Allure configuration file:
|
|
28
|
+
|
|
29
|
+
```diff
|
|
30
|
+
import { defineConfig } from "allure";
|
|
31
|
+
|
|
32
|
+
export default defineConfig({
|
|
33
|
+
name: "Allure Report",
|
|
34
|
+
output: "./allure-report",
|
|
35
|
+
historyPath: "./history.jsonl",
|
|
36
|
+
plugins: {
|
|
37
|
+
+ testops: {
|
|
38
|
+
+ options: {
|
|
39
|
+
+ launchName: "Hello, TestOps!",
|
|
40
|
+
+ launchTags: ["tag1", "tag2"],
|
|
41
|
+
+ accessToken: "your_testops_access_token",
|
|
42
|
+
+ endpoint: "https://your-testops-instance.com",
|
|
43
|
+
+ projectId: "your_testops_project_id",
|
|
44
|
+
+ },
|
|
45
|
+
+ },
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Options
|
|
51
|
+
|
|
52
|
+
The plugin accepts the following options:
|
|
53
|
+
|
|
54
|
+
| Option | Description | Type | Default |
|
|
55
|
+
|------------------|-------------------------------------------------|---------|-------------------------|
|
|
56
|
+
| `launchName` | Name of the report which will be assigned to the new launch | `string` | `Allure Report` |
|
|
57
|
+
| `launchTags` | Tags to be assigned to the new launch | `string[]` | `[]` |
|
|
58
|
+
| `accessToken` | Access token for TestOps API | `string` | `undefined` |
|
|
59
|
+
| `endpoint` | TestOps API endpoint | `string` | `undefined` |
|
|
60
|
+
| `projectId` | TestOps project ID | `string` | `undefined` |
|
|
61
|
+
|
|
62
|
+
### Using options from environment variables
|
|
63
|
+
|
|
64
|
+
The plugin automatically reads the following environment variables and uses them if not provided in the configuration (the configuration has a higher priority):
|
|
65
|
+
|
|
66
|
+
| Environment Variable | Configuration option |
|
|
67
|
+
|----------------------|----------------------|
|
|
68
|
+
| `ALLURE_TOKEN` | `accessToken` |
|
|
69
|
+
| `ALLURE_PROJECT_ID` | `projectId` |
|
|
70
|
+
| `ALLURE_ENDPOINT` | `endpoint` |
|
|
71
|
+
| `ALLURE_LAUNCH_NAME` | `launchName` |
|
|
72
|
+
| `ALLURE_LAUNCH_TAGS` | `launchTags` |
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CiDescriptor, TestResult, TestStatus } from "@allurereport/core-api";
|
|
2
|
+
export declare class TestOpsClient {
|
|
3
|
+
#private;
|
|
4
|
+
constructor(params: {
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
projectId: string;
|
|
7
|
+
accessToken: string;
|
|
8
|
+
});
|
|
9
|
+
get launchUrl(): string | undefined;
|
|
10
|
+
issueOauthToken(): Promise<void>;
|
|
11
|
+
startUpload(ci: CiDescriptor): Promise<void>;
|
|
12
|
+
stopUpload(ci: CiDescriptor, status: TestStatus): Promise<void>;
|
|
13
|
+
createLaunch(launchName: string, launchTags: string[]): Promise<void>;
|
|
14
|
+
createSession(): Promise<void>;
|
|
15
|
+
uploadTestResults(params: {
|
|
16
|
+
trs: TestResult[];
|
|
17
|
+
attachmentsResolver: (tr: TestResult) => Promise<any>;
|
|
18
|
+
fixturesResolver: (tr: TestResult) => Promise<any>;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _TestOpsClient_accessToken, _TestOpsClient_projectId, _TestOpsClient_oauthToken, _TestOpsClient_client, _TestOpsClient_launch, _TestOpsClient_session, _TestOpsClient_uploadInProgress;
|
|
13
|
+
import axios from "axios";
|
|
14
|
+
import FormData from "form-data";
|
|
15
|
+
import chunk from "lodash.chunk";
|
|
16
|
+
export class TestOpsClient {
|
|
17
|
+
constructor(params) {
|
|
18
|
+
_TestOpsClient_accessToken.set(this, void 0);
|
|
19
|
+
_TestOpsClient_projectId.set(this, void 0);
|
|
20
|
+
_TestOpsClient_oauthToken.set(this, "");
|
|
21
|
+
_TestOpsClient_client.set(this, void 0);
|
|
22
|
+
_TestOpsClient_launch.set(this, void 0);
|
|
23
|
+
_TestOpsClient_session.set(this, void 0);
|
|
24
|
+
_TestOpsClient_uploadInProgress.set(this, false);
|
|
25
|
+
if (!params.accessToken) {
|
|
26
|
+
throw new Error("accessToken is required");
|
|
27
|
+
}
|
|
28
|
+
if (!params.projectId) {
|
|
29
|
+
throw new Error("projectId is required");
|
|
30
|
+
}
|
|
31
|
+
if (!params.baseUrl) {
|
|
32
|
+
throw new Error("baseUrl is required");
|
|
33
|
+
}
|
|
34
|
+
__classPrivateFieldSet(this, _TestOpsClient_accessToken, params.accessToken, "f");
|
|
35
|
+
__classPrivateFieldSet(this, _TestOpsClient_projectId, params.projectId, "f");
|
|
36
|
+
__classPrivateFieldSet(this, _TestOpsClient_client, axios.create({
|
|
37
|
+
baseURL: params.baseUrl,
|
|
38
|
+
validateStatus: (status) => status >= 200 && status < 400,
|
|
39
|
+
}), "f");
|
|
40
|
+
}
|
|
41
|
+
get launchUrl() {
|
|
42
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
return new URL(`launch/${__classPrivateFieldGet(this, _TestOpsClient_launch, "f").id}`, __classPrivateFieldGet(this, _TestOpsClient_client, "f").defaults.baseURL).toString();
|
|
46
|
+
}
|
|
47
|
+
async issueOauthToken() {
|
|
48
|
+
const formData = new FormData();
|
|
49
|
+
formData.append("grant_type", "apitoken");
|
|
50
|
+
formData.append("scope", "openid");
|
|
51
|
+
formData.append("token", __classPrivateFieldGet(this, _TestOpsClient_accessToken, "f"));
|
|
52
|
+
const { data } = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/uaa/oauth/token", formData);
|
|
53
|
+
__classPrivateFieldSet(this, _TestOpsClient_oauthToken, data.access_token, "f");
|
|
54
|
+
}
|
|
55
|
+
async startUpload(ci) {
|
|
56
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
57
|
+
throw new Error("Launch isn't created! Call createLaunch first");
|
|
58
|
+
}
|
|
59
|
+
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/upload/start", {
|
|
60
|
+
projectId: __classPrivateFieldGet(this, _TestOpsClient_projectId, "f"),
|
|
61
|
+
ci: {
|
|
62
|
+
name: ci.type,
|
|
63
|
+
},
|
|
64
|
+
job: {
|
|
65
|
+
name: ci.jobUid,
|
|
66
|
+
uid: ci.jobUid,
|
|
67
|
+
},
|
|
68
|
+
jobRun: {
|
|
69
|
+
uid: ci.jobRunUid,
|
|
70
|
+
},
|
|
71
|
+
launch: {
|
|
72
|
+
id: __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id,
|
|
73
|
+
},
|
|
74
|
+
}, {
|
|
75
|
+
headers: {
|
|
76
|
+
Authorization: `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
__classPrivateFieldSet(this, _TestOpsClient_uploadInProgress, true, "f");
|
|
80
|
+
}
|
|
81
|
+
async stopUpload(ci, status) {
|
|
82
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_uploadInProgress, "f")) {
|
|
83
|
+
throw new Error("Upload isn't started! Call startUpload first");
|
|
84
|
+
}
|
|
85
|
+
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/upload/stop", {
|
|
86
|
+
jobRunUid: ci.jobRunUid,
|
|
87
|
+
jobUid: ci.jobUid,
|
|
88
|
+
projectId: __classPrivateFieldGet(this, _TestOpsClient_projectId, "f"),
|
|
89
|
+
status,
|
|
90
|
+
}, {
|
|
91
|
+
headers: {
|
|
92
|
+
Authorization: `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
__classPrivateFieldSet(this, _TestOpsClient_uploadInProgress, false, "f");
|
|
96
|
+
}
|
|
97
|
+
async createLaunch(launchName, launchTags) {
|
|
98
|
+
const { data } = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/launch", {
|
|
99
|
+
name: launchName,
|
|
100
|
+
projectId: __classPrivateFieldGet(this, _TestOpsClient_projectId, "f"),
|
|
101
|
+
autoclose: true,
|
|
102
|
+
external: true,
|
|
103
|
+
tags: launchTags.map((tag) => ({ name: tag })),
|
|
104
|
+
}, {
|
|
105
|
+
headers: {
|
|
106
|
+
Authorization: `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
__classPrivateFieldSet(this, _TestOpsClient_launch, data, "f");
|
|
110
|
+
}
|
|
111
|
+
async createSession() {
|
|
112
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_launch, "f")) {
|
|
113
|
+
throw new Error("Launch isn't created! Call createLaunch first");
|
|
114
|
+
}
|
|
115
|
+
const { data } = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/rs/upload/session?manual=true", {
|
|
116
|
+
launchId: __classPrivateFieldGet(this, _TestOpsClient_launch, "f").id,
|
|
117
|
+
}, {
|
|
118
|
+
headers: {
|
|
119
|
+
Authorization: `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
__classPrivateFieldSet(this, _TestOpsClient_session, data, "f");
|
|
123
|
+
}
|
|
124
|
+
async uploadTestResults(params) {
|
|
125
|
+
if (!__classPrivateFieldGet(this, _TestOpsClient_session, "f")) {
|
|
126
|
+
throw new Error("Session isn't created! Call createSession first");
|
|
127
|
+
}
|
|
128
|
+
const { trs, attachmentsResolver, fixturesResolver } = params;
|
|
129
|
+
const trsChunks = chunk(trs, 100);
|
|
130
|
+
await Promise.all(trsChunks.map(async (trsChunk) => {
|
|
131
|
+
const { data } = await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post("/api/upload/test-result", {
|
|
132
|
+
testSessionId: __classPrivateFieldGet(this, _TestOpsClient_session, "f").id,
|
|
133
|
+
results: trsChunk.map((tr) => ({
|
|
134
|
+
...tr,
|
|
135
|
+
uuid: tr.id,
|
|
136
|
+
})),
|
|
137
|
+
}, {
|
|
138
|
+
headers: {
|
|
139
|
+
"Authorization": `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`,
|
|
140
|
+
"Content-Type": "application/json",
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const trsTestOpsIdsByUuid = data.results.reduce((acc, { id, uuid }) => ({ ...acc, [uuid]: id }), {});
|
|
144
|
+
await Promise.all(trsChunk.map(async (tr) => {
|
|
145
|
+
const trTestOpsId = trsTestOpsIdsByUuid[tr.id];
|
|
146
|
+
const attachments = await attachmentsResolver(tr);
|
|
147
|
+
const fixtures = await fixturesResolver(tr);
|
|
148
|
+
if (attachments.length > 0) {
|
|
149
|
+
const attachmentsChunks = chunk(attachments, 100);
|
|
150
|
+
await Promise.all(attachmentsChunks.map(async (attachmentsChunk) => {
|
|
151
|
+
const formData = new FormData();
|
|
152
|
+
attachmentsChunk.forEach((attachment) => {
|
|
153
|
+
formData.append("file", attachment.content, {
|
|
154
|
+
filename: attachment.originalFileName,
|
|
155
|
+
contentType: attachment.contentType,
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post(`/api/upload/test-result/${trTestOpsId}/attachment`, formData, {
|
|
159
|
+
headers: {
|
|
160
|
+
Authorization: `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`,
|
|
161
|
+
...formData.getHeaders(),
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
if (fixtures.length > 0) {
|
|
167
|
+
await __classPrivateFieldGet(this, _TestOpsClient_client, "f").post(`/api/upload/test-result/${trTestOpsId}/test-fixture-result`, {
|
|
168
|
+
fixtures,
|
|
169
|
+
}, {
|
|
170
|
+
headers: {
|
|
171
|
+
Authorization: `Bearer ${__classPrivateFieldGet(this, _TestOpsClient_oauthToken, "f")}`,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}));
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
_TestOpsClient_accessToken = new WeakMap(), _TestOpsClient_projectId = new WeakMap(), _TestOpsClient_oauthToken = new WeakMap(), _TestOpsClient_client = new WeakMap(), _TestOpsClient_launch = new WeakMap(), _TestOpsClient_session = new WeakMap(), _TestOpsClient_uploadInProgress = new WeakMap();
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TestopsUploaderPlugin as default } from "./plugin.js";
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AttachmentLink, TestResult } from "@allurereport/core-api";
|
|
2
|
+
export type TestopsUploaderOptions = {
|
|
3
|
+
endpoint: string;
|
|
4
|
+
accessToken: string;
|
|
5
|
+
projectId: string;
|
|
6
|
+
launchName: string;
|
|
7
|
+
launchTags: string[];
|
|
8
|
+
filter?: (testResult: TestResult) => boolean;
|
|
9
|
+
};
|
|
10
|
+
export type TemplateManifest = Record<string, string>;
|
|
11
|
+
export type TestopsUploaderPluginOptions = TestopsUploaderOptions;
|
|
12
|
+
export type TestOpsSession = {
|
|
13
|
+
id: number;
|
|
14
|
+
jobId: number;
|
|
15
|
+
jobRunId: number;
|
|
16
|
+
launchId: number;
|
|
17
|
+
projectId: number;
|
|
18
|
+
};
|
|
19
|
+
export type TestOpsLaunch = {
|
|
20
|
+
id: number;
|
|
21
|
+
name: string;
|
|
22
|
+
closed: boolean;
|
|
23
|
+
external: boolean;
|
|
24
|
+
autoclose: boolean;
|
|
25
|
+
projectId: number;
|
|
26
|
+
tags: string[];
|
|
27
|
+
links: [];
|
|
28
|
+
issues: [];
|
|
29
|
+
createdDate: number;
|
|
30
|
+
lastModifiedDate: number;
|
|
31
|
+
};
|
|
32
|
+
export type TestResultWithAttachments = TestResult & {
|
|
33
|
+
attachments: AttachmentLink[];
|
|
34
|
+
};
|
package/dist/model.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AllureStore, type Plugin, type PluginContext, type PluginSummary } from "@allurereport/plugin-api";
|
|
2
|
+
import type { TestopsUploaderPluginOptions } from "./model.js";
|
|
3
|
+
export declare class TestopsUploaderPlugin implements Plugin {
|
|
4
|
+
#private;
|
|
5
|
+
readonly options: TestopsUploaderPluginOptions;
|
|
6
|
+
constructor(options: TestopsUploaderPluginOptions);
|
|
7
|
+
get ciMode(): boolean | undefined;
|
|
8
|
+
start(_context: PluginContext, store: AllureStore): Promise<void>;
|
|
9
|
+
update(_context: PluginContext, store: AllureStore): Promise<void>;
|
|
10
|
+
done(_context: PluginContext, store: AllureStore): Promise<void>;
|
|
11
|
+
info(context: PluginContext, store: AllureStore): Promise<PluginSummary | undefined>;
|
|
12
|
+
}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _TestopsUploaderPlugin_instances, _TestopsUploaderPlugin_ci, _TestopsUploaderPlugin_client, _TestopsUploaderPlugin_launchName, _TestopsUploaderPlugin_launchTags, _TestopsUploaderPlugin_uploadedTestResultsIds, _TestopsUploaderPlugin_upload, _TestopsUploaderPlugin_startUpload, _TestopsUploaderPlugin_stopUpload;
|
|
13
|
+
import { detect } from "@allurereport/ci";
|
|
14
|
+
import { getWorstStatus } from "@allurereport/core-api";
|
|
15
|
+
import { convertToSummaryTestResult, } from "@allurereport/plugin-api";
|
|
16
|
+
import { TestOpsClient } from "./client.js";
|
|
17
|
+
import { resolvePluginOptions, unwrapStepsAttachments } from "./utils.js";
|
|
18
|
+
export class TestopsUploaderPlugin {
|
|
19
|
+
constructor(options) {
|
|
20
|
+
_TestopsUploaderPlugin_instances.add(this);
|
|
21
|
+
this.options = options;
|
|
22
|
+
_TestopsUploaderPlugin_ci.set(this, void 0);
|
|
23
|
+
_TestopsUploaderPlugin_client.set(this, void 0);
|
|
24
|
+
_TestopsUploaderPlugin_launchName.set(this, "");
|
|
25
|
+
_TestopsUploaderPlugin_launchTags.set(this, []);
|
|
26
|
+
_TestopsUploaderPlugin_uploadedTestResultsIds.set(this, []);
|
|
27
|
+
const { accessToken, endpoint, projectId, launchName, launchTags } = resolvePluginOptions(options);
|
|
28
|
+
__classPrivateFieldSet(this, _TestopsUploaderPlugin_ci, detect(), "f");
|
|
29
|
+
if ([accessToken, endpoint, projectId].every(Boolean)) {
|
|
30
|
+
__classPrivateFieldSet(this, _TestopsUploaderPlugin_client, new TestOpsClient({
|
|
31
|
+
baseUrl: endpoint,
|
|
32
|
+
accessToken,
|
|
33
|
+
projectId,
|
|
34
|
+
}), "f");
|
|
35
|
+
__classPrivateFieldSet(this, _TestopsUploaderPlugin_launchName, launchName, "f");
|
|
36
|
+
__classPrivateFieldSet(this, _TestopsUploaderPlugin_launchTags, launchTags, "f");
|
|
37
|
+
}
|
|
38
|
+
if (!accessToken) {
|
|
39
|
+
console.warn("TestOps access token is missing. Please provide a valid access token in the plugin options.");
|
|
40
|
+
}
|
|
41
|
+
if (!endpoint) {
|
|
42
|
+
console.warn("TestOps endpoint is missing. Please provide a valid endpoint in the plugin options.");
|
|
43
|
+
}
|
|
44
|
+
if (!projectId) {
|
|
45
|
+
console.warn("TestOps project ID is missing. Please provide a valid project ID in the plugin options.");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
get ciMode() {
|
|
49
|
+
return __classPrivateFieldGet(this, _TestopsUploaderPlugin_ci, "f") && __classPrivateFieldGet(this, _TestopsUploaderPlugin_ci, "f").type !== "local";
|
|
50
|
+
}
|
|
51
|
+
async start(_context, store) {
|
|
52
|
+
if (!__classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f")) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
await __classPrivateFieldGet(this, _TestopsUploaderPlugin_instances, "m", _TestopsUploaderPlugin_startUpload).call(this);
|
|
56
|
+
await __classPrivateFieldGet(this, _TestopsUploaderPlugin_instances, "m", _TestopsUploaderPlugin_upload).call(this, store, { issueNewToken: false });
|
|
57
|
+
console.info(`TestOps launch has been created: ${__classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f").launchUrl}`);
|
|
58
|
+
}
|
|
59
|
+
async update(_context, store) {
|
|
60
|
+
if (!__classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f")) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
await __classPrivateFieldGet(this, _TestopsUploaderPlugin_instances, "m", _TestopsUploaderPlugin_upload).call(this, store);
|
|
64
|
+
}
|
|
65
|
+
async done(_context, store) {
|
|
66
|
+
if (!__classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f")) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const allTrs = (await store.allTestResults()).filter((tr) => this.options.filter ? this.options.filter(tr) : true);
|
|
70
|
+
const worstStatus = getWorstStatus(allTrs.map(({ status }) => status));
|
|
71
|
+
await __classPrivateFieldGet(this, _TestopsUploaderPlugin_instances, "m", _TestopsUploaderPlugin_upload).call(this, store);
|
|
72
|
+
await __classPrivateFieldGet(this, _TestopsUploaderPlugin_instances, "m", _TestopsUploaderPlugin_stopUpload).call(this, worstStatus || "unknown");
|
|
73
|
+
}
|
|
74
|
+
async info(context, store) {
|
|
75
|
+
if (!__classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f")?.launchUrl) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const allTrs = (await store.allTestResults()).filter((tr) => this.options.filter ? this.options.filter(tr) : true);
|
|
79
|
+
const newTrs = await store.allNewTestResults();
|
|
80
|
+
const retryTrs = allTrs.filter((tr) => !!tr?.retries?.length);
|
|
81
|
+
const flakyTrs = allTrs.filter((tr) => !!tr?.flaky);
|
|
82
|
+
const duration = allTrs.reduce((acc, { duration: trDuration = 0 }) => acc + trDuration, 0);
|
|
83
|
+
const worstStatus = getWorstStatus(allTrs.map(({ status }) => status));
|
|
84
|
+
const createdAt = allTrs.reduce((acc, { stop }) => Math.max(acc, stop || 0), 0);
|
|
85
|
+
return {
|
|
86
|
+
name: __classPrivateFieldGet(this, _TestopsUploaderPlugin_launchName, "f"),
|
|
87
|
+
remoteHref: __classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f").launchUrl,
|
|
88
|
+
stats: await store.testsStatistic(this.options.filter),
|
|
89
|
+
status: worstStatus ?? "passed",
|
|
90
|
+
duration,
|
|
91
|
+
createdAt,
|
|
92
|
+
plugin: "Awesome",
|
|
93
|
+
newTests: newTrs.map(convertToSummaryTestResult),
|
|
94
|
+
flakyTests: flakyTrs.map(convertToSummaryTestResult),
|
|
95
|
+
retryTests: retryTrs.map(convertToSummaryTestResult),
|
|
96
|
+
meta: {
|
|
97
|
+
reportId: context.reportUuid,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
_TestopsUploaderPlugin_ci = new WeakMap(), _TestopsUploaderPlugin_client = new WeakMap(), _TestopsUploaderPlugin_launchName = new WeakMap(), _TestopsUploaderPlugin_launchTags = new WeakMap(), _TestopsUploaderPlugin_uploadedTestResultsIds = new WeakMap(), _TestopsUploaderPlugin_instances = new WeakSet(), _TestopsUploaderPlugin_upload = async function _TestopsUploaderPlugin_upload(store, options) {
|
|
103
|
+
const { issueNewToken = true } = options ?? {};
|
|
104
|
+
const allTrs = await store.allTestResults();
|
|
105
|
+
const trsToUpload = allTrs.filter((tr) => {
|
|
106
|
+
const uploaded = __classPrivateFieldGet(this, _TestopsUploaderPlugin_uploadedTestResultsIds, "f").includes(tr.id);
|
|
107
|
+
if (this.options.filter) {
|
|
108
|
+
return this.options.filter(tr) && !uploaded;
|
|
109
|
+
}
|
|
110
|
+
return !uploaded;
|
|
111
|
+
});
|
|
112
|
+
if (trsToUpload.length === 0) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const allTrsWithAttachments = trsToUpload.map((tr) => {
|
|
116
|
+
return {
|
|
117
|
+
...tr,
|
|
118
|
+
steps: unwrapStepsAttachments(tr.steps),
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
if (issueNewToken) {
|
|
122
|
+
await __classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f").issueOauthToken();
|
|
123
|
+
}
|
|
124
|
+
await __classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f").createSession();
|
|
125
|
+
await __classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f").uploadTestResults({
|
|
126
|
+
trs: allTrsWithAttachments,
|
|
127
|
+
attachmentsResolver: async (tr) => {
|
|
128
|
+
const attachments = await store.attachmentsByTrId(tr.id);
|
|
129
|
+
return await Promise.all(attachments.map(async (attachment) => {
|
|
130
|
+
const content = await store.attachmentContentById(attachment.id);
|
|
131
|
+
return {
|
|
132
|
+
originalFileName: attachment.originalFileName,
|
|
133
|
+
contentType: attachment.contentType,
|
|
134
|
+
content: await content?.readContent(async (s) => s),
|
|
135
|
+
};
|
|
136
|
+
}));
|
|
137
|
+
},
|
|
138
|
+
fixturesResolver: async (tr) => {
|
|
139
|
+
const fxts = await store.fixturesByTrId(tr.id);
|
|
140
|
+
return fxts.map((fxt) => ({
|
|
141
|
+
...fxt,
|
|
142
|
+
type: fxt.type.toUpperCase(),
|
|
143
|
+
steps: unwrapStepsAttachments(fxt.steps),
|
|
144
|
+
}));
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
__classPrivateFieldGet(this, _TestopsUploaderPlugin_uploadedTestResultsIds, "f").push(...allTrsWithAttachments.map((tr) => tr.id));
|
|
148
|
+
}, _TestopsUploaderPlugin_startUpload = async function _TestopsUploaderPlugin_startUpload() {
|
|
149
|
+
if (!__classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f")) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
await __classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f").issueOauthToken();
|
|
153
|
+
await __classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f").createLaunch(__classPrivateFieldGet(this, _TestopsUploaderPlugin_launchName, "f"), __classPrivateFieldGet(this, _TestopsUploaderPlugin_launchTags, "f"));
|
|
154
|
+
if (!this.ciMode) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
await __classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f").startUpload(__classPrivateFieldGet(this, _TestopsUploaderPlugin_ci, "f"));
|
|
158
|
+
}, _TestopsUploaderPlugin_stopUpload = async function _TestopsUploaderPlugin_stopUpload(status) {
|
|
159
|
+
if (!this.ciMode || !__classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f")) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
await __classPrivateFieldGet(this, _TestopsUploaderPlugin_client, "f").stopUpload(__classPrivateFieldGet(this, _TestopsUploaderPlugin_ci, "f"), status);
|
|
163
|
+
};
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { TestStepResult } from "@allurereport/core-api";
|
|
2
|
+
import type { TestopsUploaderPluginOptions } from "./model.js";
|
|
3
|
+
export declare const unwrapStepsAttachments: (steps: TestStepResult[]) => TestStepResult[];
|
|
4
|
+
export declare const resolvePluginOptions: (options: TestopsUploaderPluginOptions) => Omit<TestopsUploaderPluginOptions, "filter">;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { env } from "node:process";
|
|
2
|
+
export const unwrapStepsAttachments = (steps) => {
|
|
3
|
+
return steps.map((step) => {
|
|
4
|
+
if (step.type === "attachment") {
|
|
5
|
+
return {
|
|
6
|
+
...step,
|
|
7
|
+
attachment: step.link,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
if (step.steps) {
|
|
11
|
+
return {
|
|
12
|
+
...step,
|
|
13
|
+
steps: unwrapStepsAttachments(step.steps),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return step;
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
export const resolvePluginOptions = (options) => {
|
|
20
|
+
const { ALLURE_TOKEN, ALLURE_ENDPOINT, ALLURE_PROJECT_ID, ALLURE_LAUNCH_TAGS, ALLURE_LAUNCH_NAME } = env;
|
|
21
|
+
const { accessToken = ALLURE_TOKEN, endpoint = ALLURE_ENDPOINT, projectId = ALLURE_PROJECT_ID, launchTags = ALLURE_LAUNCH_TAGS, launchName = ALLURE_LAUNCH_NAME, } = options;
|
|
22
|
+
const tags = !launchTags
|
|
23
|
+
? []
|
|
24
|
+
: Array.isArray(launchTags)
|
|
25
|
+
? launchTags
|
|
26
|
+
: launchTags.split(",").map((tag) => tag.trim());
|
|
27
|
+
return {
|
|
28
|
+
launchName: launchName || "Allure Report",
|
|
29
|
+
launchTags: tags,
|
|
30
|
+
accessToken: accessToken || "",
|
|
31
|
+
endpoint: endpoint || "",
|
|
32
|
+
projectId: projectId || "",
|
|
33
|
+
};
|
|
34
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@allurereport/plugin-testops",
|
|
3
|
+
"version": "3.1.0",
|
|
4
|
+
"description": "Allure Plugin TestOps",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"allure",
|
|
7
|
+
"testing",
|
|
8
|
+
"report",
|
|
9
|
+
"plugin",
|
|
10
|
+
"html",
|
|
11
|
+
"testops",
|
|
12
|
+
"uploader"
|
|
13
|
+
],
|
|
14
|
+
"repository": "https://github.com/allure-framework/allure3",
|
|
15
|
+
"license": "Apache-2.0",
|
|
16
|
+
"author": "Qameta Software",
|
|
17
|
+
"type": "module",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"module": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"files": [
|
|
25
|
+
"./dist"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "run clean && tsc --project ./tsconfig.json",
|
|
29
|
+
"clean": "rimraf ./dist",
|
|
30
|
+
"eslint": "eslint ./src/**/*.{js,jsx,ts,tsx}",
|
|
31
|
+
"eslint:format": "eslint --fix ./src/**/*.{js,jsx,ts,tsx}",
|
|
32
|
+
"test": "rimraf ./out && vitest run"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@allurereport/ci": "3.1.0",
|
|
36
|
+
"@allurereport/core-api": "3.1.0",
|
|
37
|
+
"@allurereport/plugin-api": "3.1.0",
|
|
38
|
+
"@allurereport/reader-api": "3.1.0",
|
|
39
|
+
"axios": "^1.13.2",
|
|
40
|
+
"form-data": "^4.0.5",
|
|
41
|
+
"lodash.chunk": "^4.2.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@stylistic/eslint-plugin": "^2.6.1",
|
|
45
|
+
"@types/d3-shape": "^3.1.6",
|
|
46
|
+
"@types/eslint": "^8.56.11",
|
|
47
|
+
"@types/form-data": "^2.5.2",
|
|
48
|
+
"@types/lodash.chunk": "^4",
|
|
49
|
+
"@types/node": "^20.17.9",
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
51
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
52
|
+
"@vitest/runner": "^2.1.9",
|
|
53
|
+
"@vitest/snapshot": "^2.1.9",
|
|
54
|
+
"allure-vitest": "^3.3.3",
|
|
55
|
+
"eslint": "^8.57.0",
|
|
56
|
+
"eslint-config-prettier": "^9.1.0",
|
|
57
|
+
"eslint-plugin-import": "^2.29.1",
|
|
58
|
+
"eslint-plugin-jsdoc": "^50.0.0",
|
|
59
|
+
"eslint-plugin-n": "^17.10.1",
|
|
60
|
+
"eslint-plugin-no-null": "^1.0.2",
|
|
61
|
+
"eslint-plugin-prefer-arrow": "^1.2.3",
|
|
62
|
+
"rimraf": "^6.0.1",
|
|
63
|
+
"typescript": "^5.6.3",
|
|
64
|
+
"vitest": "^2.1.9"
|
|
65
|
+
}
|
|
66
|
+
}
|