allure 3.4.1 → 3.6.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 +17 -0
- package/dist/commands/agent.d.ts +58 -0
- package/dist/commands/agent.js +374 -0
- package/dist/commands/commons/run.d.ts +48 -0
- package/dist/commands/commons/run.js +258 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.js +1 -0
- package/dist/commands/run.d.ts +0 -7
- package/dist/commands/run.js +51 -238
- package/dist/index.js +5 -1
- package/dist/utils/agent-select.d.ts +41 -0
- package/dist/utils/agent-select.js +141 -0
- package/dist/utils/agent-state.d.ts +15 -0
- package/dist/utils/agent-state.js +83 -0
- package/dist/utils/environment.d.ts +7 -2
- package/dist/utils/environment.js +6 -1
- package/dist/utils/execution-context.d.ts +4 -0
- package/dist/utils/execution-context.js +9 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/process.js +2 -1
- package/package.json +22 -21
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import * as console from "node:console";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { QualityGateState, stringifyQualityGateResults } from "@allurereport/core";
|
|
8
|
+
import { createTestPlan } from "@allurereport/core-api";
|
|
9
|
+
import { allureResultsDirectoriesWatcher, delayedFileProcessingWatcher, newFilesInDirectoryWatcher, } from "@allurereport/directory-watcher";
|
|
10
|
+
import { BufferResultFile, PathResultFile } from "@allurereport/reader-api";
|
|
11
|
+
import { KnownError } from "@allurereport/service";
|
|
12
|
+
import { red } from "yoctocolors";
|
|
13
|
+
import { logTests, runProcess, terminationOf } from "../../utils/index.js";
|
|
14
|
+
import { logError } from "../../utils/logs.js";
|
|
15
|
+
import { stopProcessTree } from "../../utils/process.js";
|
|
16
|
+
export const executeNestedAllureCommand = async (params) => {
|
|
17
|
+
const nestedProcess = runProcess({
|
|
18
|
+
command: params.command,
|
|
19
|
+
commandArgs: params.commandArgs,
|
|
20
|
+
cwd: params.cwd,
|
|
21
|
+
environmentVariables: params.environmentVariables,
|
|
22
|
+
logs: params.silent ? "ignore" : "inherit",
|
|
23
|
+
});
|
|
24
|
+
return await terminationOf(nestedProcess);
|
|
25
|
+
};
|
|
26
|
+
export const runTests = async (params) => {
|
|
27
|
+
const { allureReport, knownIssues, cwd, command, commandArgs, logs, environmentVariables, environment, withQualityGate, silent, logProcessExit = true, } = params;
|
|
28
|
+
let testProcessStarted = false;
|
|
29
|
+
const allureResultsWatchers = new Map();
|
|
30
|
+
const processWatcher = delayedFileProcessingWatcher(async (path) => {
|
|
31
|
+
await allureReport.readResult(new PathResultFile(path));
|
|
32
|
+
}, {
|
|
33
|
+
indexDelay: 200,
|
|
34
|
+
minProcessingDelay: 1000,
|
|
35
|
+
});
|
|
36
|
+
const allureResultsWatch = allureResultsDirectoriesWatcher(cwd, async (newAllureResults, deletedAllureResults) => {
|
|
37
|
+
for (const delAr of deletedAllureResults) {
|
|
38
|
+
const watcher = allureResultsWatchers.get(delAr);
|
|
39
|
+
if (watcher) {
|
|
40
|
+
await watcher.abort();
|
|
41
|
+
}
|
|
42
|
+
allureResultsWatchers.delete(delAr);
|
|
43
|
+
}
|
|
44
|
+
for (const newAr of newAllureResults) {
|
|
45
|
+
if (allureResultsWatchers.has(newAr)) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const watcher = newFilesInDirectoryWatcher(newAr, async (path) => {
|
|
49
|
+
await processWatcher.addFile(path);
|
|
50
|
+
}, {
|
|
51
|
+
ignoreInitial: !testProcessStarted,
|
|
52
|
+
indexDelay: 300,
|
|
53
|
+
});
|
|
54
|
+
allureResultsWatchers.set(newAr, watcher);
|
|
55
|
+
await watcher.initialScan();
|
|
56
|
+
}
|
|
57
|
+
}, { indexDelay: 600 });
|
|
58
|
+
await allureResultsWatch.initialScan();
|
|
59
|
+
testProcessStarted = true;
|
|
60
|
+
const beforeProcess = Date.now();
|
|
61
|
+
const testProcess = runProcess({
|
|
62
|
+
command,
|
|
63
|
+
commandArgs,
|
|
64
|
+
cwd,
|
|
65
|
+
environmentVariables,
|
|
66
|
+
logs,
|
|
67
|
+
});
|
|
68
|
+
const qualityGateState = new QualityGateState();
|
|
69
|
+
let qualityGateUnsub;
|
|
70
|
+
let qualityGateResults = [];
|
|
71
|
+
let testProcessStdout = "";
|
|
72
|
+
let testProcessStderr = "";
|
|
73
|
+
if (withQualityGate) {
|
|
74
|
+
qualityGateUnsub = allureReport.realtimeSubscriber.onTestResults(async (testResults) => {
|
|
75
|
+
const trs = await Promise.all(testResults.map((tr) => allureReport.store.testResultById(tr)));
|
|
76
|
+
const filteredTrs = trs.filter((tr) => tr !== undefined);
|
|
77
|
+
if (!filteredTrs.length) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const { results, fastFailed } = await allureReport.validate({
|
|
81
|
+
trs: filteredTrs,
|
|
82
|
+
state: qualityGateState,
|
|
83
|
+
environment,
|
|
84
|
+
knownIssues,
|
|
85
|
+
});
|
|
86
|
+
if (!fastFailed) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
allureReport.realtimeDispatcher.sendQualityGateResults(results);
|
|
90
|
+
qualityGateResults = results;
|
|
91
|
+
try {
|
|
92
|
+
await stopProcessTree(testProcess.pid);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
if (err.message.includes("kill ESRCH")) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (logs === "pipe") {
|
|
103
|
+
testProcess.stdout?.setEncoding("utf8").on?.("data", (data) => {
|
|
104
|
+
testProcessStdout += data;
|
|
105
|
+
if (silent) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
process.stdout.write(data);
|
|
109
|
+
});
|
|
110
|
+
testProcess.stderr?.setEncoding("utf8").on?.("data", async (data) => {
|
|
111
|
+
testProcessStderr += data;
|
|
112
|
+
if (silent) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
process.stderr.write(data);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const code = await terminationOf(testProcess);
|
|
119
|
+
const afterProcess = Date.now();
|
|
120
|
+
if (logProcessExit) {
|
|
121
|
+
if (code !== null) {
|
|
122
|
+
console.log(`process finished with code ${code} (${afterProcess - beforeProcess}ms)`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log(`process terminated (${afterProcess - beforeProcess}ms)`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
await allureResultsWatch.abort();
|
|
129
|
+
for (const [ar, watcher] of allureResultsWatchers) {
|
|
130
|
+
await watcher.abort();
|
|
131
|
+
allureResultsWatchers.delete(ar);
|
|
132
|
+
}
|
|
133
|
+
await processWatcher.abort();
|
|
134
|
+
qualityGateUnsub?.();
|
|
135
|
+
return {
|
|
136
|
+
code,
|
|
137
|
+
stdout: testProcessStdout,
|
|
138
|
+
stderr: testProcessStderr,
|
|
139
|
+
qualityGateResults,
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
export const executeAllureRun = async (params) => {
|
|
143
|
+
const { allureReport, knownIssues, cwd, command, commandArgs, environmentVariables = {}, environment, withQualityGate, silent, logs, ignoreLogs, maxRerun = 0, logProcessExit = true, } = params;
|
|
144
|
+
await allureReport.start();
|
|
145
|
+
const globalExitCode = {
|
|
146
|
+
original: 0,
|
|
147
|
+
actual: undefined,
|
|
148
|
+
};
|
|
149
|
+
let qualityGateResults = [];
|
|
150
|
+
let testProcessResult = null;
|
|
151
|
+
try {
|
|
152
|
+
testProcessResult = await runTests({
|
|
153
|
+
logs,
|
|
154
|
+
silent,
|
|
155
|
+
allureReport,
|
|
156
|
+
knownIssues,
|
|
157
|
+
cwd,
|
|
158
|
+
command,
|
|
159
|
+
commandArgs,
|
|
160
|
+
environment,
|
|
161
|
+
environmentVariables,
|
|
162
|
+
withQualityGate,
|
|
163
|
+
logProcessExit,
|
|
164
|
+
});
|
|
165
|
+
for (let rerun = 0; rerun < maxRerun; rerun++) {
|
|
166
|
+
const failed = await allureReport.store.failedTestResults();
|
|
167
|
+
if (failed.length === 0) {
|
|
168
|
+
console.log("no failed tests is detected.");
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
const testPlan = createTestPlan(failed);
|
|
172
|
+
console.log(`rerun number ${rerun} of ${testPlan.tests.length} tests:`);
|
|
173
|
+
logTests(failed);
|
|
174
|
+
const tmpDir = await mkdtemp(join(tmpdir(), "allure-run-"));
|
|
175
|
+
const testPlanPath = resolve(tmpDir, `${rerun}-testplan.json`);
|
|
176
|
+
await writeFile(testPlanPath, JSON.stringify(testPlan));
|
|
177
|
+
testProcessResult = await runTests({
|
|
178
|
+
silent,
|
|
179
|
+
logs,
|
|
180
|
+
allureReport,
|
|
181
|
+
knownIssues,
|
|
182
|
+
cwd,
|
|
183
|
+
command,
|
|
184
|
+
commandArgs,
|
|
185
|
+
environment,
|
|
186
|
+
environmentVariables: {
|
|
187
|
+
...environmentVariables,
|
|
188
|
+
ALLURE_TESTPLAN_PATH: testPlanPath,
|
|
189
|
+
ALLURE_RERUN: `${rerun}`,
|
|
190
|
+
},
|
|
191
|
+
withQualityGate,
|
|
192
|
+
logProcessExit,
|
|
193
|
+
});
|
|
194
|
+
await rm(tmpDir, { recursive: true });
|
|
195
|
+
logTests(await allureReport.store.allTestResults());
|
|
196
|
+
}
|
|
197
|
+
const trs = await allureReport.store.allTestResults({ includeHidden: false });
|
|
198
|
+
qualityGateResults = testProcessResult?.qualityGateResults ?? [];
|
|
199
|
+
if (withQualityGate && !qualityGateResults.length) {
|
|
200
|
+
const { results } = await allureReport.validate({
|
|
201
|
+
trs,
|
|
202
|
+
knownIssues,
|
|
203
|
+
environment,
|
|
204
|
+
});
|
|
205
|
+
qualityGateResults = results;
|
|
206
|
+
}
|
|
207
|
+
if (qualityGateResults.length) {
|
|
208
|
+
const qualityGateMessage = stringifyQualityGateResults(qualityGateResults);
|
|
209
|
+
console.error(qualityGateMessage);
|
|
210
|
+
allureReport.realtimeDispatcher.sendQualityGateResults(qualityGateResults);
|
|
211
|
+
}
|
|
212
|
+
globalExitCode.original = testProcessResult?.code ?? -1;
|
|
213
|
+
if (withQualityGate) {
|
|
214
|
+
globalExitCode.actual = qualityGateResults.length > 0 ? 1 : 0;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
globalExitCode.actual = 1;
|
|
219
|
+
if (error instanceof KnownError) {
|
|
220
|
+
console.error(red(error.message));
|
|
221
|
+
allureReport.realtimeDispatcher.sendGlobalError({
|
|
222
|
+
message: error.message,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
await logError("Failed to run tests using Allure due to unexpected error", error);
|
|
227
|
+
allureReport.realtimeDispatcher.sendGlobalError({
|
|
228
|
+
message: error.message,
|
|
229
|
+
trace: error.stack,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const processFailed = Math.abs(globalExitCode.actual ?? globalExitCode.original) !== 0;
|
|
234
|
+
if (!ignoreLogs && testProcessResult?.stdout) {
|
|
235
|
+
const fileName = randomUUID();
|
|
236
|
+
const stdoutResultFile = new BufferResultFile(Buffer.from(testProcessResult.stdout, "utf8"), `${fileName}`);
|
|
237
|
+
stdoutResultFile.contentType = "text/plain";
|
|
238
|
+
allureReport.realtimeDispatcher.sendGlobalAttachment(stdoutResultFile, "stdout.txt");
|
|
239
|
+
}
|
|
240
|
+
if (!ignoreLogs && testProcessResult?.stderr) {
|
|
241
|
+
const fileName = randomUUID();
|
|
242
|
+
const stderrResultFile = new BufferResultFile(Buffer.from(testProcessResult.stderr, "utf8"), fileName);
|
|
243
|
+
stderrResultFile.contentType = "text/plain";
|
|
244
|
+
allureReport.realtimeDispatcher.sendGlobalAttachment(stderrResultFile, "stderr.txt");
|
|
245
|
+
if (processFailed) {
|
|
246
|
+
allureReport.realtimeDispatcher.sendGlobalError({
|
|
247
|
+
message: "Test process has failed",
|
|
248
|
+
trace: testProcessResult.stderr,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
allureReport.realtimeDispatcher.sendGlobalExitCode(globalExitCode);
|
|
253
|
+
await allureReport.done();
|
|
254
|
+
return {
|
|
255
|
+
globalExitCode,
|
|
256
|
+
testProcessResult,
|
|
257
|
+
};
|
|
258
|
+
};
|
package/dist/commands/index.d.ts
CHANGED
package/dist/commands/index.js
CHANGED
package/dist/commands/run.d.ts
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import type { QualityGateValidationResult } from "@allurereport/plugin-api";
|
|
2
1
|
import { Command } from "clipanion";
|
|
3
|
-
export type TestProcessResult = {
|
|
4
|
-
code: number | null;
|
|
5
|
-
stdout: string;
|
|
6
|
-
stderr: string;
|
|
7
|
-
qualityGateResults: QualityGateValidationResult[];
|
|
8
|
-
};
|
|
9
2
|
export declare class RunCommand extends Command {
|
|
10
3
|
static paths: string[][];
|
|
11
4
|
static usage: import("clipanion").Usage;
|
package/dist/commands/run.js
CHANGED
|
@@ -1,136 +1,16 @@
|
|
|
1
1
|
import * as console from "node:console";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join, resolve } from "node:path";
|
|
2
|
+
import { realpath, rm } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
6
4
|
import process, { exit } from "node:process";
|
|
7
|
-
import { AllureReport,
|
|
8
|
-
import { createTestPlan } from "@allurereport/core-api";
|
|
9
|
-
import { allureResultsDirectoriesWatcher, delayedFileProcessingWatcher, newFilesInDirectoryWatcher, } from "@allurereport/directory-watcher";
|
|
5
|
+
import { AllureReport, isFileNotFoundError, readConfig } from "@allurereport/core";
|
|
10
6
|
import Awesome from "@allurereport/plugin-awesome";
|
|
11
|
-
import { BufferResultFile, PathResultFile } from "@allurereport/reader-api";
|
|
12
|
-
import { KnownError } from "@allurereport/service";
|
|
13
7
|
import { serve } from "@allurereport/static-server";
|
|
14
8
|
import { Command, Option, UsageError } from "clipanion";
|
|
15
9
|
import { red } from "yoctocolors";
|
|
16
10
|
import { environmentNameOption, environmentOption, normalizeCommandEnvironmentOptions, resolveCommandEnvironment, } from "../utils/environment.js";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
const runTests = async (params) => {
|
|
21
|
-
const { allureReport, knownIssues, cwd, command, commandArgs, logs, environmentVariables, environment, withQualityGate, silent, } = params;
|
|
22
|
-
let testProcessStarted = false;
|
|
23
|
-
const allureResultsWatchers = new Map();
|
|
24
|
-
const processWatcher = delayedFileProcessingWatcher(async (path) => {
|
|
25
|
-
await allureReport.readResult(new PathResultFile(path));
|
|
26
|
-
}, {
|
|
27
|
-
indexDelay: 200,
|
|
28
|
-
minProcessingDelay: 1000,
|
|
29
|
-
});
|
|
30
|
-
const allureResultsWatch = allureResultsDirectoriesWatcher(cwd, async (newAllureResults, deletedAllureResults) => {
|
|
31
|
-
for (const delAr of deletedAllureResults) {
|
|
32
|
-
const watcher = allureResultsWatchers.get(delAr);
|
|
33
|
-
if (watcher) {
|
|
34
|
-
await watcher.abort();
|
|
35
|
-
}
|
|
36
|
-
allureResultsWatchers.delete(delAr);
|
|
37
|
-
}
|
|
38
|
-
for (const newAr of newAllureResults) {
|
|
39
|
-
if (allureResultsWatchers.has(newAr)) {
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
const watcher = newFilesInDirectoryWatcher(newAr, async (path) => {
|
|
43
|
-
await processWatcher.addFile(path);
|
|
44
|
-
}, {
|
|
45
|
-
ignoreInitial: !testProcessStarted,
|
|
46
|
-
indexDelay: 300,
|
|
47
|
-
});
|
|
48
|
-
allureResultsWatchers.set(newAr, watcher);
|
|
49
|
-
await watcher.initialScan();
|
|
50
|
-
}
|
|
51
|
-
}, { indexDelay: 600 });
|
|
52
|
-
await allureResultsWatch.initialScan();
|
|
53
|
-
testProcessStarted = true;
|
|
54
|
-
const beforeProcess = Date.now();
|
|
55
|
-
const testProcess = runProcess({
|
|
56
|
-
command,
|
|
57
|
-
commandArgs,
|
|
58
|
-
cwd,
|
|
59
|
-
environmentVariables,
|
|
60
|
-
logs,
|
|
61
|
-
});
|
|
62
|
-
const qualityGateState = new QualityGateState();
|
|
63
|
-
let qualityGateUnsub;
|
|
64
|
-
let qualityGateResults = [];
|
|
65
|
-
let testProcessStdout = "";
|
|
66
|
-
let testProcessStderr = "";
|
|
67
|
-
if (withQualityGate) {
|
|
68
|
-
qualityGateUnsub = allureReport.realtimeSubscriber.onTestResults(async (testResults) => {
|
|
69
|
-
const trs = await Promise.all(testResults.map((tr) => allureReport.store.testResultById(tr)));
|
|
70
|
-
const filteredTrs = trs.filter((tr) => tr !== undefined);
|
|
71
|
-
if (!filteredTrs.length) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const { results, fastFailed } = await allureReport.validate({
|
|
75
|
-
trs: filteredTrs,
|
|
76
|
-
state: qualityGateState,
|
|
77
|
-
environment,
|
|
78
|
-
knownIssues,
|
|
79
|
-
});
|
|
80
|
-
if (!fastFailed) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
allureReport.realtimeDispatcher.sendQualityGateResults(results);
|
|
84
|
-
qualityGateResults = results;
|
|
85
|
-
try {
|
|
86
|
-
await stopProcessTree(testProcess.pid);
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
if (err.message.includes("kill ESRCH")) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
throw err;
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
if (logs === "pipe") {
|
|
97
|
-
testProcess.stdout?.setEncoding("utf8").on?.("data", (data) => {
|
|
98
|
-
testProcessStdout += data;
|
|
99
|
-
if (silent) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
process.stdout.write(data);
|
|
103
|
-
});
|
|
104
|
-
testProcess.stderr?.setEncoding("utf8").on?.("data", async (data) => {
|
|
105
|
-
testProcessStderr += data;
|
|
106
|
-
if (silent) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
process.stderr.write(data);
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
const code = await terminationOf(testProcess);
|
|
113
|
-
const afterProcess = Date.now();
|
|
114
|
-
if (code !== null) {
|
|
115
|
-
console.log(`process finished with code ${code} (${afterProcess - beforeProcess}ms)`);
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
console.log(`process terminated (${afterProcess - beforeProcess}ms)`);
|
|
119
|
-
}
|
|
120
|
-
await allureResultsWatch.abort();
|
|
121
|
-
for (const [ar, watcher] of allureResultsWatchers) {
|
|
122
|
-
await watcher.abort();
|
|
123
|
-
allureResultsWatchers.delete(ar);
|
|
124
|
-
}
|
|
125
|
-
await processWatcher.abort();
|
|
126
|
-
qualityGateUnsub?.();
|
|
127
|
-
return {
|
|
128
|
-
code,
|
|
129
|
-
stdout: testProcessStdout,
|
|
130
|
-
stderr: testProcessStderr,
|
|
131
|
-
qualityGateResults,
|
|
132
|
-
};
|
|
133
|
-
};
|
|
11
|
+
import { createChildAllureCliEnvironment, getActiveAllureCliCommand } from "../utils/execution-context.js";
|
|
12
|
+
import { executeAgentMode } from "./agent.js";
|
|
13
|
+
import { executeAllureRun, executeNestedAllureCommand } from "./commons/run.js";
|
|
134
14
|
export class RunCommand extends Command {
|
|
135
15
|
constructor() {
|
|
136
16
|
super(...arguments);
|
|
@@ -185,11 +65,22 @@ export class RunCommand extends Command {
|
|
|
185
65
|
if (!args || !args.length) {
|
|
186
66
|
throw new UsageError("expecting command to be specified after --, e.g. allure run -- npm run test");
|
|
187
67
|
}
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
68
|
+
const legacyAgentOutput = process.env.ALLURE_AGENT_OUTPUT;
|
|
69
|
+
if (legacyAgentOutput) {
|
|
70
|
+
await executeAgentMode({
|
|
71
|
+
configPath: this.config,
|
|
72
|
+
cwd: this.cwd,
|
|
73
|
+
output: resolve(process.cwd(), legacyAgentOutput),
|
|
74
|
+
expectations: process.env.ALLURE_AGENT_EXPECTATIONS
|
|
75
|
+
? resolve(process.cwd(), process.env.ALLURE_AGENT_EXPECTATIONS)
|
|
76
|
+
: undefined,
|
|
77
|
+
environment: this.environment,
|
|
78
|
+
environmentName: this.environmentName,
|
|
79
|
+
silent: this.silent,
|
|
80
|
+
args,
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
193
84
|
const before = new Date().getTime();
|
|
194
85
|
process.on("exit", (exitCode) => {
|
|
195
86
|
const after = new Date().getTime();
|
|
@@ -200,6 +91,21 @@ export class RunCommand extends Command {
|
|
|
200
91
|
const cwd = await realpath(this.cwd ?? process.cwd());
|
|
201
92
|
const hideLabels = this.hideLabels?.length ? this.hideLabels : undefined;
|
|
202
93
|
console.log(`${command} ${commandArgs.join(" ")}`);
|
|
94
|
+
if (getActiveAllureCliCommand()) {
|
|
95
|
+
const exitCode = await executeNestedAllureCommand({
|
|
96
|
+
command,
|
|
97
|
+
commandArgs,
|
|
98
|
+
cwd,
|
|
99
|
+
silent: this.silent,
|
|
100
|
+
});
|
|
101
|
+
exit(exitCode ?? -1);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const environmentOptions = {
|
|
105
|
+
environment: this.environment,
|
|
106
|
+
environmentName: this.environmentName,
|
|
107
|
+
};
|
|
108
|
+
normalizeCommandEnvironmentOptions(environmentOptions);
|
|
203
109
|
const maxRerun = this.rerun ? parseInt(this.rerun, 10) : 0;
|
|
204
110
|
const config = await readConfig(cwd, this.config, {
|
|
205
111
|
output: this.output,
|
|
@@ -246,113 +152,20 @@ export class RunCommand extends Command {
|
|
|
246
152
|
],
|
|
247
153
|
});
|
|
248
154
|
const knownIssues = await allureReport.store.allKnownIssues();
|
|
249
|
-
await
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
command,
|
|
264
|
-
commandArgs,
|
|
265
|
-
environment: resolvedEnvironment?.id,
|
|
266
|
-
environmentVariables: {},
|
|
267
|
-
withQualityGate,
|
|
268
|
-
});
|
|
269
|
-
for (let rerun = 0; rerun < maxRerun; rerun++) {
|
|
270
|
-
const failed = await allureReport.store.failedTestResults();
|
|
271
|
-
if (failed.length === 0) {
|
|
272
|
-
console.log("no failed tests is detected.");
|
|
273
|
-
break;
|
|
274
|
-
}
|
|
275
|
-
const testPlan = createTestPlan(failed);
|
|
276
|
-
console.log(`rerun number ${rerun} of ${testPlan.tests.length} tests:`);
|
|
277
|
-
logTests(failed);
|
|
278
|
-
const tmpDir = await mkdtemp(join(tmpdir(), "allure-run-"));
|
|
279
|
-
const testPlanPath = resolve(tmpDir, `${rerun}-testplan.json`);
|
|
280
|
-
await writeFile(testPlanPath, JSON.stringify(testPlan));
|
|
281
|
-
testProcessResult = await runTests({
|
|
282
|
-
silent: this.silent,
|
|
283
|
-
logs: this.logs,
|
|
284
|
-
allureReport,
|
|
285
|
-
knownIssues,
|
|
286
|
-
cwd,
|
|
287
|
-
command,
|
|
288
|
-
commandArgs,
|
|
289
|
-
environment: resolvedEnvironment?.id,
|
|
290
|
-
environmentVariables: {
|
|
291
|
-
ALLURE_TESTPLAN_PATH: testPlanPath,
|
|
292
|
-
ALLURE_RERUN: `${rerun}`,
|
|
293
|
-
},
|
|
294
|
-
withQualityGate,
|
|
295
|
-
});
|
|
296
|
-
await rm(tmpDir, { recursive: true });
|
|
297
|
-
logTests(await allureReport.store.allTestResults());
|
|
298
|
-
}
|
|
299
|
-
const trs = await allureReport.store.allTestResults({ includeHidden: false });
|
|
300
|
-
qualityGateResults = testProcessResult?.qualityGateResults ?? [];
|
|
301
|
-
if (withQualityGate && !qualityGateResults?.length) {
|
|
302
|
-
const { results } = await allureReport.validate({
|
|
303
|
-
trs,
|
|
304
|
-
knownIssues,
|
|
305
|
-
environment: resolvedEnvironment?.id,
|
|
306
|
-
});
|
|
307
|
-
qualityGateResults = results;
|
|
308
|
-
}
|
|
309
|
-
if (qualityGateResults?.length) {
|
|
310
|
-
const qualityGateMessage = stringifyQualityGateResults(qualityGateResults);
|
|
311
|
-
console.error(qualityGateMessage);
|
|
312
|
-
allureReport.realtimeDispatcher.sendQualityGateResults(qualityGateResults);
|
|
313
|
-
}
|
|
314
|
-
globalExitCode.original = testProcessResult?.code ?? -1;
|
|
315
|
-
if (withQualityGate) {
|
|
316
|
-
globalExitCode.actual = qualityGateResults.length > 0 ? 1 : 0;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
catch (error) {
|
|
320
|
-
globalExitCode.actual = 1;
|
|
321
|
-
if (error instanceof KnownError) {
|
|
322
|
-
console.error(red(error.message));
|
|
323
|
-
allureReport.realtimeDispatcher.sendGlobalError({
|
|
324
|
-
message: error.message,
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
await logError("Failed to run tests using Allure due to unexpected error", error);
|
|
329
|
-
allureReport.realtimeDispatcher.sendGlobalError({
|
|
330
|
-
message: error.message,
|
|
331
|
-
trace: error.stack,
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
const processFailed = Math.abs(globalExitCode.actual ?? globalExitCode.original) !== 0;
|
|
336
|
-
if (!this.ignoreLogs && testProcessResult?.stdout) {
|
|
337
|
-
const fileName = randomUUID();
|
|
338
|
-
const stdoutResultFile = new BufferResultFile(Buffer.from(testProcessResult.stdout, "utf8"), `${fileName}`);
|
|
339
|
-
stdoutResultFile.contentType = "text/plain";
|
|
340
|
-
allureReport.realtimeDispatcher.sendGlobalAttachment(stdoutResultFile, "stdout.txt");
|
|
341
|
-
}
|
|
342
|
-
if (!this.ignoreLogs && testProcessResult?.stderr) {
|
|
343
|
-
const fileName = randomUUID();
|
|
344
|
-
const stderrResultFile = new BufferResultFile(Buffer.from(testProcessResult.stderr, "utf8"), fileName);
|
|
345
|
-
stderrResultFile.contentType = "text/plain";
|
|
346
|
-
allureReport.realtimeDispatcher.sendGlobalAttachment(stderrResultFile, "stderr.txt");
|
|
347
|
-
if (processFailed) {
|
|
348
|
-
allureReport.realtimeDispatcher.sendGlobalError({
|
|
349
|
-
message: "Test process has failed",
|
|
350
|
-
trace: testProcessResult.stderr,
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
allureReport.realtimeDispatcher.sendGlobalExitCode(globalExitCode);
|
|
355
|
-
await allureReport.done();
|
|
155
|
+
const { globalExitCode } = await executeAllureRun({
|
|
156
|
+
allureReport,
|
|
157
|
+
knownIssues,
|
|
158
|
+
cwd,
|
|
159
|
+
command,
|
|
160
|
+
commandArgs,
|
|
161
|
+
environmentVariables: createChildAllureCliEnvironment("run"),
|
|
162
|
+
environment: resolvedEnvironment?.id,
|
|
163
|
+
withQualityGate,
|
|
164
|
+
logs: this.logs,
|
|
165
|
+
silent: this.silent,
|
|
166
|
+
ignoreLogs: this.ignoreLogs,
|
|
167
|
+
maxRerun,
|
|
168
|
+
});
|
|
356
169
|
if (config.open) {
|
|
357
170
|
await serve({
|
|
358
171
|
port: config.port ? parseInt(config.port, 10) : undefined,
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { argv } from "node:process";
|
|
3
3
|
import { Builtins, Cli } from "clipanion";
|
|
4
|
-
import { Allure2Command, AwesomeCommand, ClassicCommand, CsvCommand, DashboardCommand, GenerateCommand, HistoryCommand, JiraClearCommand, KnownIssueCommand, LogCommand, OpenCommand, QualityGateCommand, ResultsPackCommand, ResultsUnpackCommand, RunCommand, SlackCommand, TestPlanCommand, WatchCommand, WhoamiCommand, } from "./commands/index.js";
|
|
4
|
+
import { AgentCommand, AgentLatestCommand, AgentSelectCommand, AgentStateDirCommand, Allure2Command, AwesomeCommand, ClassicCommand, CsvCommand, DashboardCommand, GenerateCommand, HistoryCommand, JiraClearCommand, KnownIssueCommand, LogCommand, OpenCommand, QualityGateCommand, ResultsPackCommand, ResultsUnpackCommand, RunCommand, SlackCommand, TestPlanCommand, WatchCommand, WhoamiCommand, } from "./commands/index.js";
|
|
5
5
|
const [node, app, ...args] = argv;
|
|
6
6
|
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
7
7
|
const cli = new Cli({
|
|
@@ -11,6 +11,10 @@ const cli = new Cli({
|
|
|
11
11
|
});
|
|
12
12
|
cli.register(AwesomeCommand);
|
|
13
13
|
cli.register(Allure2Command);
|
|
14
|
+
cli.register(AgentLatestCommand);
|
|
15
|
+
cli.register(AgentSelectCommand);
|
|
16
|
+
cli.register(AgentStateDirCommand);
|
|
17
|
+
cli.register(AgentCommand);
|
|
14
18
|
cli.register(ClassicCommand);
|
|
15
19
|
cli.register(CsvCommand);
|
|
16
20
|
cli.register(DashboardCommand);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { TestPlan } from "@allurereport/core-api";
|
|
2
|
+
import { type AgentTestManifestLine } from "@allurereport/plugin-agent";
|
|
3
|
+
export type AgentRerunPreset = "review" | "failed" | "unsuccessful" | "all";
|
|
4
|
+
export type AgentLabelFilter = {
|
|
5
|
+
name: string;
|
|
6
|
+
value: string;
|
|
7
|
+
};
|
|
8
|
+
export type AgentSelectionResult = {
|
|
9
|
+
outputDir: string;
|
|
10
|
+
preset: AgentRerunPreset;
|
|
11
|
+
selectedTests: AgentTestManifestLine[];
|
|
12
|
+
testPlan: TestPlan;
|
|
13
|
+
};
|
|
14
|
+
export type AgentTestPlanContext = {
|
|
15
|
+
outputDir: string;
|
|
16
|
+
preset: AgentRerunPreset;
|
|
17
|
+
selectedCount: number;
|
|
18
|
+
testPlanPath: string;
|
|
19
|
+
cleanup: () => Promise<void>;
|
|
20
|
+
};
|
|
21
|
+
export declare const normalizeAgentRerunPreset: (value?: string) => AgentRerunPreset;
|
|
22
|
+
export declare const parseAgentLabelFilters: (values?: string[]) => AgentLabelFilter[];
|
|
23
|
+
export declare const resolveAgentSelectionOutputDir: (params: {
|
|
24
|
+
cwd: string;
|
|
25
|
+
from?: string;
|
|
26
|
+
latest?: boolean;
|
|
27
|
+
}) => Promise<string>;
|
|
28
|
+
export declare const selectAgentTestPlan: (params: {
|
|
29
|
+
outputDir: string;
|
|
30
|
+
preset?: AgentRerunPreset;
|
|
31
|
+
environments?: string[];
|
|
32
|
+
labelFilters?: AgentLabelFilter[];
|
|
33
|
+
}) => Promise<AgentSelectionResult>;
|
|
34
|
+
export declare const createAgentTestPlanContext: (params: {
|
|
35
|
+
cwd: string;
|
|
36
|
+
from?: string;
|
|
37
|
+
latest?: boolean;
|
|
38
|
+
preset?: AgentRerunPreset;
|
|
39
|
+
environments?: string[];
|
|
40
|
+
labelFilters?: AgentLabelFilter[];
|
|
41
|
+
}) => Promise<AgentTestPlanContext | undefined>;
|