allure 3.19.0 → 3.20.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 +5 -0
- package/dist/commands/commons/generate.d.ts +1 -0
- package/dist/commands/commons/generate.js +17 -3
- package/dist/commands/commons/run.d.ts +6 -0
- package/dist/commands/commons/run.js +110 -43
- package/dist/commands/generate.d.ts +1 -1
- package/dist/commands/generate.js +7 -2
- package/dist/commands/gitlab/generate.d.ts +2 -1
- package/dist/commands/gitlab/generate.js +7 -3
- package/dist/commands/run.js +10 -14
- package/dist/rules.d.ts +1 -1
- package/dist/rules.js +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/logs.d.ts +1 -1
- package/dist/utils/logs.js +5 -3
- package/package.json +23 -22
- package/dist/utils/terminal.d.ts +0 -3
- package/dist/utils/terminal.js +0 -20
package/README.md
CHANGED
|
@@ -68,6 +68,11 @@ Repeat `--results-dir` for multiple patterns. CLI overrides `config.resultsDir`.
|
|
|
68
68
|
|
|
69
69
|
After the tests complete, the report is generated automatically. Existing results from previous runs are ignored, as Allure 3 focuses solely on new data to ensure accurate and up-to-date reporting.
|
|
70
70
|
|
|
71
|
+
Allure operational messages use scoped prefixes such as `[AllureRun]:`, `[QualityGate]:`, and `[AllureRerun]:`.
|
|
72
|
+
Set `ALLURE_LOG_LEVEL=debug` to include details such as selected tests and temporary test plan paths. Allure ignores
|
|
73
|
+
the generic `LOG_LEVEL` environment variable. Use `--silent` to hide the nested test process output while keeping
|
|
74
|
+
Allure lifecycle messages visible.
|
|
75
|
+
|
|
71
76
|
### Running Tests In Agent Mode
|
|
72
77
|
|
|
73
78
|
When you need agent-friendly markdown output for review, debugging, or scope validation, use the `agent` command:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { exit } from "node:process";
|
|
2
2
|
import { createReportContext } from "@allurereport/ci";
|
|
3
|
-
import { AllureReport } from "@allurereport/core";
|
|
3
|
+
import { AllureReport, filterFailedQualityGateResults, stringifyQualityGateResults } from "@allurereport/core";
|
|
4
4
|
import { KnownError } from "@allurereport/service";
|
|
5
5
|
import { red } from "yoctocolors";
|
|
6
6
|
import { findFilesByGlobs } from "../../utils/fileSystem.js";
|
|
@@ -26,11 +26,25 @@ export const generate = async (params) => {
|
|
|
26
26
|
for (const dir of resultDirectories) {
|
|
27
27
|
await allureReport.readDirectory(dir);
|
|
28
28
|
}
|
|
29
|
+
let exitCode = 0;
|
|
30
|
+
if (allureReport.hasQualityGate) {
|
|
31
|
+
const trs = await allureReport.store.allTestResults({ includeRetries: false });
|
|
32
|
+
const { results } = await allureReport.validate({
|
|
33
|
+
trs,
|
|
34
|
+
environment: params.config.environment,
|
|
35
|
+
});
|
|
36
|
+
const qualityGateMessage = stringifyQualityGateResults(results);
|
|
37
|
+
if (qualityGateMessage) {
|
|
38
|
+
console.error(qualityGateMessage);
|
|
39
|
+
}
|
|
40
|
+
allureReport.realtimeDispatcher.sendQualityGateResults(results);
|
|
41
|
+
exitCode = filterFailedQualityGateResults(results).length > 0 ? 1 : 0;
|
|
42
|
+
}
|
|
29
43
|
await allureReport.done();
|
|
30
44
|
if (!params.collectSummary) {
|
|
31
|
-
return;
|
|
45
|
+
return { exitCode };
|
|
32
46
|
}
|
|
33
|
-
const result = {};
|
|
47
|
+
const result = { exitCode };
|
|
34
48
|
try {
|
|
35
49
|
const summary = await createReportContext(params.config.output, {
|
|
36
50
|
onError: (message) => console.warn(message),
|
|
@@ -8,6 +8,10 @@ export type TestProcessResult = {
|
|
|
8
8
|
fastFailed: boolean;
|
|
9
9
|
};
|
|
10
10
|
export type RunLogsMode = "pipe" | "inherit" | "ignore";
|
|
11
|
+
type RunAttempt = {
|
|
12
|
+
current: number;
|
|
13
|
+
total: number;
|
|
14
|
+
};
|
|
11
15
|
export declare const executeNestedAllureCommand: (params: {
|
|
12
16
|
command: string;
|
|
13
17
|
commandArgs: string[];
|
|
@@ -26,6 +30,7 @@ export declare const runTests: (params: {
|
|
|
26
30
|
silent?: boolean;
|
|
27
31
|
logs?: RunLogsMode;
|
|
28
32
|
logProcessExit?: boolean;
|
|
33
|
+
attempt?: RunAttempt;
|
|
29
34
|
resultsPatterns?: readonly string[];
|
|
30
35
|
}) => Promise<TestProcessResult | null>;
|
|
31
36
|
export declare const executeAllureRun: (params: {
|
|
@@ -46,3 +51,4 @@ export declare const executeAllureRun: (params: {
|
|
|
46
51
|
globalExitCode: ExitCode;
|
|
47
52
|
testProcessResult: TestProcessResult | null;
|
|
48
53
|
}>;
|
|
54
|
+
export {};
|
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
import * as console from "node:console";
|
|
2
1
|
import { randomUUID } from "node:crypto";
|
|
3
2
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
4
3
|
import { tmpdir } from "node:os";
|
|
5
4
|
import { join, resolve } from "node:path";
|
|
6
5
|
import process from "node:process";
|
|
6
|
+
import { Logger } from "@allurereport/cli-commons";
|
|
7
7
|
import { QualityGateState, filterFailedQualityGateResults, stringifyQualityGateResults, } from "@allurereport/core";
|
|
8
|
-
import { createTestPlan } from "@allurereport/core-api";
|
|
8
|
+
import { createTestPlan, formatDuration } from "@allurereport/core-api";
|
|
9
9
|
import { allureResultsDirectoriesWatcher, delayedFileProcessingWatcher, newFilesInDirectoryWatcher, } from "@allurereport/directory-watcher";
|
|
10
10
|
import { formatProcessLogAttachmentName } from "@allurereport/plugin-agent";
|
|
11
11
|
import { BufferResultFile, PathResultFile } from "@allurereport/reader-api";
|
|
12
12
|
import { KnownError } from "@allurereport/service";
|
|
13
|
-
import {
|
|
14
|
-
import { logTests, runProcess, terminationOf } from "../../utils/index.js";
|
|
13
|
+
import { runProcess, terminationOf } from "../../utils/index.js";
|
|
15
14
|
import { logError } from "../../utils/logs.js";
|
|
16
15
|
import { stopProcessTree } from "../../utils/process.js";
|
|
17
16
|
import { allureResultsDirectoriesGlobWatcher } from "./resultsDiscovery.js";
|
|
17
|
+
const runLogger = new Logger("AllureRun");
|
|
18
|
+
const qualityGateLogger = new Logger("QualityGate");
|
|
19
|
+
const rerunLogger = new Logger("AllureRerun");
|
|
18
20
|
export const executeNestedAllureCommand = async (params) => {
|
|
19
21
|
const nestedProcess = runProcess({
|
|
20
22
|
command: params.command,
|
|
@@ -34,7 +36,7 @@ const attachResultsDirectoryWatchers = (params) => {
|
|
|
34
36
|
return allureResultsDirectoriesWatcher(params.cwd, params.onUpdate, { indexDelay: 600 });
|
|
35
37
|
};
|
|
36
38
|
export const runTests = async (params) => {
|
|
37
|
-
const { allureReport, cwd, command, commandArgs, logs, environmentVariables, environment, withQualityGate, silent, logProcessExit = true, resultsPatterns = [], } = params;
|
|
39
|
+
const { allureReport, cwd, command, commandArgs, logs, environmentVariables, environment, withQualityGate, silent, logProcessExit = true, attempt = { current: 1, total: 1 }, resultsPatterns = [], } = params;
|
|
38
40
|
let testProcessStarted = false;
|
|
39
41
|
const allureResultsWatchers = new Map();
|
|
40
42
|
const processWatcher = delayedFileProcessingWatcher(async (path) => {
|
|
@@ -72,6 +74,16 @@ export const runTests = async (params) => {
|
|
|
72
74
|
await allureResultsWatch.initialScan();
|
|
73
75
|
testProcessStarted = true;
|
|
74
76
|
const beforeProcess = Date.now();
|
|
77
|
+
const commandLine = [command, ...commandArgs].join(" ");
|
|
78
|
+
const rerunsEnabled = attempt.total > 1;
|
|
79
|
+
if (logProcessExit) {
|
|
80
|
+
if (rerunsEnabled) {
|
|
81
|
+
runLogger.info(`Attempt ${attempt.current}/${attempt.total} started: ${commandLine}`);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
runLogger.info(`Running: ${commandLine}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
75
87
|
const testProcess = runProcess({
|
|
76
88
|
command,
|
|
77
89
|
commandArgs,
|
|
@@ -107,6 +119,10 @@ export const runTests = async (params) => {
|
|
|
107
119
|
fastFailTriggered = true;
|
|
108
120
|
qualityGateResults = results;
|
|
109
121
|
qualityGateUnsub = undefined;
|
|
122
|
+
const failedRules = filterFailedQualityGateResults(results).map(({ rule }) => rule);
|
|
123
|
+
const failedRulesMessage = failedRules.length > 0 ? `: ${failedRules.join(", ")}` : "";
|
|
124
|
+
const stopTarget = rerunsEnabled ? `attempt ${attempt.current}/${attempt.total}` : "test process";
|
|
125
|
+
qualityGateLogger.info(`Fast-fail triggered${failedRulesMessage}; stopping ${stopTarget}`);
|
|
110
126
|
try {
|
|
111
127
|
await stopProcessTree(testProcess.pid);
|
|
112
128
|
}
|
|
@@ -136,12 +152,16 @@ export const runTests = async (params) => {
|
|
|
136
152
|
}
|
|
137
153
|
const code = await terminationOf(testProcess);
|
|
138
154
|
const afterProcess = Date.now();
|
|
139
|
-
if (logProcessExit) {
|
|
140
|
-
|
|
141
|
-
|
|
155
|
+
if (logProcessExit && rerunsEnabled) {
|
|
156
|
+
const duration = formatDuration(afterProcess - beforeProcess);
|
|
157
|
+
if (fastFailTriggered) {
|
|
158
|
+
runLogger.info(`Attempt ${attempt.current}/${attempt.total} stopped by Quality Gate after ${duration}`);
|
|
159
|
+
}
|
|
160
|
+
else if (code !== null) {
|
|
161
|
+
runLogger.info(`Attempt ${attempt.current}/${attempt.total} finished with code ${code} after ${duration}`);
|
|
142
162
|
}
|
|
143
163
|
else {
|
|
144
|
-
|
|
164
|
+
runLogger.warn(`Attempt ${attempt.current}/${attempt.total} terminated after ${duration}`);
|
|
145
165
|
}
|
|
146
166
|
}
|
|
147
167
|
await allureResultsWatch.abort();
|
|
@@ -159,6 +179,11 @@ export const runTests = async (params) => {
|
|
|
159
179
|
fastFailed: fastFailTriggered,
|
|
160
180
|
};
|
|
161
181
|
};
|
|
182
|
+
const relatedQualityGateTestResults = async (allureReport, qualityGateResults) => {
|
|
183
|
+
const testResultIds = new Set(filterFailedQualityGateResults(qualityGateResults).flatMap(({ testResults }) => testResults));
|
|
184
|
+
const testResults = await Promise.all([...testResultIds].map((id) => allureReport.store.testResultById(id)));
|
|
185
|
+
return testResults.filter((testResult) => testResult !== undefined);
|
|
186
|
+
};
|
|
162
187
|
const publishProcessGlobals = (params) => {
|
|
163
188
|
const { allureReport, command, commandArgs, ignoreLogs, processFailed, testProcessResult } = params;
|
|
164
189
|
if (!ignoreLogs && testProcessResult?.stdout) {
|
|
@@ -182,6 +207,7 @@ const publishProcessGlobals = (params) => {
|
|
|
182
207
|
};
|
|
183
208
|
export const executeAllureRun = async (params) => {
|
|
184
209
|
const { allureReport, cwd, command, commandArgs, environmentVariables = {}, environment, withQualityGate, silent, logs, ignoreLogs, maxRerun = 0, logProcessExit = true, resultsPatterns = [], } = params;
|
|
210
|
+
const totalAttempts = maxRerun + 1;
|
|
185
211
|
await allureReport.start();
|
|
186
212
|
const globalExitCode = {
|
|
187
213
|
original: 0,
|
|
@@ -201,6 +227,10 @@ export const executeAllureRun = async (params) => {
|
|
|
201
227
|
environmentVariables,
|
|
202
228
|
withQualityGate,
|
|
203
229
|
logProcessExit,
|
|
230
|
+
attempt: {
|
|
231
|
+
current: 1,
|
|
232
|
+
total: totalAttempts,
|
|
233
|
+
},
|
|
204
234
|
resultsPatterns,
|
|
205
235
|
});
|
|
206
236
|
const allFailuresAfterInitialRun = await allureReport.store.failedTestResults();
|
|
@@ -218,37 +248,75 @@ export const executeAllureRun = async (params) => {
|
|
|
218
248
|
testProcessResult,
|
|
219
249
|
});
|
|
220
250
|
}
|
|
221
|
-
for (let rerun = 0; rerun < maxRerun && testProcessResult
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
251
|
+
for (let rerun = 0; rerun < maxRerun && testProcessResult; rerun++) {
|
|
252
|
+
const nextAttempt = {
|
|
253
|
+
current: rerun + 2,
|
|
254
|
+
total: totalAttempts,
|
|
255
|
+
};
|
|
256
|
+
const fullRerun = testProcessResult.fastFailed;
|
|
257
|
+
let testResultsToRerun = [];
|
|
258
|
+
if (!fullRerun) {
|
|
259
|
+
const blockingFailures = await allureReport.store.blockingFailedTestResults();
|
|
260
|
+
let relatedTestResults = [];
|
|
261
|
+
if (withQualityGate) {
|
|
262
|
+
const currentTestResults = await allureReport.store.allTestResults({ includeRetries: false });
|
|
263
|
+
const { results } = await allureReport.validate({
|
|
264
|
+
trs: currentTestResults,
|
|
265
|
+
environment,
|
|
266
|
+
});
|
|
267
|
+
relatedTestResults = await relatedQualityGateTestResults(allureReport, results);
|
|
268
|
+
}
|
|
269
|
+
testResultsToRerun = [
|
|
270
|
+
...new Map([...blockingFailures, ...relatedTestResults].map((testResult) => [testResult.id, testResult])).values(),
|
|
271
|
+
];
|
|
272
|
+
if (testResultsToRerun.length === 0) {
|
|
273
|
+
rerunLogger.info("No blocking failures or failed Quality Gate-related tests remain; no further reruns are needed");
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const testPlan = createTestPlan(testResultsToRerun);
|
|
278
|
+
if (fullRerun) {
|
|
279
|
+
rerunLogger.warn(`Attempt ${nextAttempt.current}/${nextAttempt.total}: Quality Gate fast-fail interrupted the previous attempt; restarting full test process`);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
const testWord = testPlan.tests.length === 1 ? "test" : "tests";
|
|
283
|
+
rerunLogger.info(`Attempt ${nextAttempt.current}/${nextAttempt.total}: rerunning ${testPlan.tests.length} failed, broken, or Quality Gate-related ${testWord}`);
|
|
284
|
+
testResultsToRerun.forEach(({ fullName, status }) => {
|
|
285
|
+
rerunLogger.debug(`${fullName} (${status})`);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
const tmpDir = fullRerun ? undefined : await mkdtemp(join(tmpdir(), "allure-run-"));
|
|
289
|
+
const testPlanPath = tmpDir ? resolve(tmpDir, `${rerun}-testplan.json`) : undefined;
|
|
290
|
+
try {
|
|
291
|
+
if (testPlanPath) {
|
|
292
|
+
await writeFile(testPlanPath, JSON.stringify(testPlan));
|
|
293
|
+
rerunLogger.debug(`Test plan: ${testPlanPath}`);
|
|
294
|
+
}
|
|
295
|
+
allureReport.realtimeDispatcher.sendProcessGlobalsReset();
|
|
296
|
+
testProcessResult = await runTests({
|
|
297
|
+
silent,
|
|
298
|
+
logs,
|
|
299
|
+
allureReport,
|
|
300
|
+
cwd,
|
|
301
|
+
command,
|
|
302
|
+
commandArgs,
|
|
303
|
+
environment,
|
|
304
|
+
environmentVariables: {
|
|
305
|
+
...environmentVariables,
|
|
306
|
+
...(testPlanPath ? { ALLURE_TESTPLAN_PATH: testPlanPath } : {}),
|
|
307
|
+
ALLURE_RERUN: `${rerun}`,
|
|
308
|
+
},
|
|
309
|
+
withQualityGate,
|
|
310
|
+
logProcessExit,
|
|
311
|
+
attempt: nextAttempt,
|
|
312
|
+
resultsPatterns,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
finally {
|
|
316
|
+
if (tmpDir) {
|
|
317
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
318
|
+
}
|
|
226
319
|
}
|
|
227
|
-
const testPlan = createTestPlan(failed);
|
|
228
|
-
console.log(`rerun number ${rerun} of ${testPlan.tests.length} tests:`);
|
|
229
|
-
logTests(failed);
|
|
230
|
-
const tmpDir = await mkdtemp(join(tmpdir(), "allure-run-"));
|
|
231
|
-
const testPlanPath = resolve(tmpDir, `${rerun}-testplan.json`);
|
|
232
|
-
await writeFile(testPlanPath, JSON.stringify(testPlan));
|
|
233
|
-
allureReport.realtimeDispatcher.sendProcessGlobalsReset();
|
|
234
|
-
testProcessResult = await runTests({
|
|
235
|
-
silent,
|
|
236
|
-
logs,
|
|
237
|
-
allureReport,
|
|
238
|
-
cwd,
|
|
239
|
-
command,
|
|
240
|
-
commandArgs,
|
|
241
|
-
environment,
|
|
242
|
-
environmentVariables: {
|
|
243
|
-
...environmentVariables,
|
|
244
|
-
ALLURE_TESTPLAN_PATH: testPlanPath,
|
|
245
|
-
ALLURE_RERUN: `${rerun}`,
|
|
246
|
-
},
|
|
247
|
-
withQualityGate,
|
|
248
|
-
logProcessExit,
|
|
249
|
-
resultsPatterns,
|
|
250
|
-
});
|
|
251
|
-
await rm(tmpDir, { recursive: true });
|
|
252
320
|
const allFailuresAfterRerun = await allureReport.store.failedTestResults();
|
|
253
321
|
const blockingFailuresAfterRerun = await allureReport.store.blockingFailedTestResults();
|
|
254
322
|
publishProcessGlobals({
|
|
@@ -261,7 +329,6 @@ export const executeAllureRun = async (params) => {
|
|
|
261
329
|
: (testProcessResult?.code ?? -1)) !== 0,
|
|
262
330
|
testProcessResult,
|
|
263
331
|
});
|
|
264
|
-
logTests(await allureReport.store.allTestResults());
|
|
265
332
|
}
|
|
266
333
|
const allFailuresAfterReruns = await allureReport.store.failedTestResults();
|
|
267
334
|
const blockingFailuresAfterReruns = await allureReport.store.blockingFailedTestResults();
|
|
@@ -280,7 +347,7 @@ export const executeAllureRun = async (params) => {
|
|
|
280
347
|
if (qualityGateResults.length) {
|
|
281
348
|
const qualityGateMessage = stringifyQualityGateResults(qualityGateResults);
|
|
282
349
|
if (qualityGateMessage) {
|
|
283
|
-
|
|
350
|
+
qualityGateLogger.error(qualityGateMessage);
|
|
284
351
|
}
|
|
285
352
|
allureReport.realtimeDispatcher.sendQualityGateResults(qualityGateResults);
|
|
286
353
|
}
|
|
@@ -292,13 +359,13 @@ export const executeAllureRun = async (params) => {
|
|
|
292
359
|
catch (error) {
|
|
293
360
|
globalExitCode.actual = 1;
|
|
294
361
|
if (error instanceof KnownError) {
|
|
295
|
-
|
|
362
|
+
runLogger.error(error.message);
|
|
296
363
|
allureReport.realtimeDispatcher.sendGlobalError({
|
|
297
364
|
message: error.message,
|
|
298
365
|
});
|
|
299
366
|
}
|
|
300
367
|
else {
|
|
301
|
-
await logError("Failed to run tests using Allure due to unexpected error", error);
|
|
368
|
+
await logError("Failed to run tests using Allure due to unexpected error", error, (message) => runLogger.error(message));
|
|
302
369
|
allureReport.realtimeDispatcher.sendGlobalError({
|
|
303
370
|
message: error.message,
|
|
304
371
|
trace: error.stack,
|
|
@@ -59,12 +59,15 @@ export class GenerateCommand extends Command {
|
|
|
59
59
|
...(this.historyBaseUrl !== undefined ? { historyBaseUrl: this.historyBaseUrl } : {}),
|
|
60
60
|
resolutions: { knownIssuesPath: this.knownIssues },
|
|
61
61
|
});
|
|
62
|
-
await generate({
|
|
62
|
+
const result = await generate({
|
|
63
63
|
dump: this.dump,
|
|
64
64
|
resultsDir: this.resultsDir,
|
|
65
65
|
cwd,
|
|
66
66
|
config,
|
|
67
67
|
});
|
|
68
|
+
if (!result) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
68
71
|
if (config.open) {
|
|
69
72
|
await serve({
|
|
70
73
|
port: config.port ? parseInt(config.port, 10) : undefined,
|
|
@@ -72,12 +75,14 @@ export class GenerateCommand extends Command {
|
|
|
72
75
|
open: true,
|
|
73
76
|
});
|
|
74
77
|
}
|
|
78
|
+
return result.exitCode;
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
GenerateCommand.paths = [["generate"]];
|
|
78
82
|
GenerateCommand.usage = Command.Usage({
|
|
79
83
|
description: "Generates the report in the specified directory.",
|
|
80
|
-
details: "This command generates a report from the provided Allure Results directories."
|
|
84
|
+
details: "This command generates a report from the provided Allure Results directories. " +
|
|
85
|
+
"When a quality gate is configured, the command validates the loaded results and finishes writing the report or dump before returning its status.",
|
|
81
86
|
examples: [
|
|
82
87
|
["generate ./allure-results", "Generate a report from the ./allure-results directory"],
|
|
83
88
|
[
|
|
@@ -10,6 +10,7 @@ export declare class GitlabGenerateCommand extends Command {
|
|
|
10
10
|
historyPath: string | undefined;
|
|
11
11
|
historyLimit: string | undefined;
|
|
12
12
|
historyBaseUrl: string | undefined;
|
|
13
|
+
summaryComment: boolean;
|
|
13
14
|
gitlabToken: string | undefined;
|
|
14
|
-
execute(): Promise<
|
|
15
|
+
execute(): Promise<0 | 1 | undefined>;
|
|
15
16
|
}
|
|
@@ -66,6 +66,9 @@ export class GitlabGenerateCommand extends Command {
|
|
|
66
66
|
this.historyBaseUrl = Option.String("--history-base-url", {
|
|
67
67
|
description: "The public base URL of the generated report directory",
|
|
68
68
|
});
|
|
69
|
+
this.summaryComment = Option.Boolean("--summary-comment", true, {
|
|
70
|
+
description: "Post a report summary comment in the merge request (default: true). Disable with --no-summary-comment",
|
|
71
|
+
});
|
|
69
72
|
this.gitlabToken = Option.String("--gitlab-token", {
|
|
70
73
|
description: "GitLab api token with api write permissions",
|
|
71
74
|
});
|
|
@@ -111,11 +114,11 @@ export class GitlabGenerateCommand extends Command {
|
|
|
111
114
|
}
|
|
112
115
|
const reportUrl = `${config.historyBaseUrl}/index.html`;
|
|
113
116
|
console.log(`GitLab report URL: ${reportUrl}`);
|
|
114
|
-
if (result.summary && existsSync(join(config.output, "index.html"))) {
|
|
117
|
+
if (this.summaryComment && result.summary && existsSync(join(config.output, "index.html"))) {
|
|
115
118
|
console.log("Posting report summary comment");
|
|
116
119
|
if (!token) {
|
|
117
120
|
console.log(" no API token provided, skipping");
|
|
118
|
-
return;
|
|
121
|
+
return result.exitCode;
|
|
119
122
|
}
|
|
120
123
|
const { summary } = result;
|
|
121
124
|
await runGitlabOperation(" failed to add summary comment", () => upsertGitlabJobNote({
|
|
@@ -124,12 +127,13 @@ export class GitlabGenerateCommand extends Command {
|
|
|
124
127
|
reportUrl,
|
|
125
128
|
}));
|
|
126
129
|
}
|
|
130
|
+
return result.exitCode;
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
133
|
GitlabGenerateCommand.paths = [["gitlab"]];
|
|
130
134
|
GitlabGenerateCommand.usage = Command.Usage({
|
|
131
135
|
category: "Integrations",
|
|
132
|
-
description: "Generate test report and post report summary in merge request comments",
|
|
136
|
+
description: "Generate test report and optionally post report summary in merge request comments",
|
|
133
137
|
details: "This command generates a report from the provided Allure Results directories. When api access token is configured, " +
|
|
134
138
|
"integration will post summary as comment for merge request pipelines and attempt to lookup history file from previously executed job ." +
|
|
135
139
|
"This integration is designed to be executed from within GitLab CI job.",
|
package/dist/commands/run.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import * as console from "node:console";
|
|
2
1
|
import { realpath, rm } from "node:fs/promises";
|
|
3
2
|
import process, { exit } from "node:process";
|
|
3
|
+
import { Logger } from "@allurereport/cli-commons";
|
|
4
4
|
import { AllureReport, isFileNotFoundError, readConfig } from "@allurereport/core";
|
|
5
|
+
import { formatDuration } from "@allurereport/core-api";
|
|
5
6
|
import Awesome from "@allurereport/plugin-awesome";
|
|
6
7
|
import { serve } from "@allurereport/static-server";
|
|
7
8
|
import { Command, Option, UsageError } from "clipanion";
|
|
@@ -10,6 +11,7 @@ import { createChildAllureCliEnvironment, getActiveAllureCliCommand } from "../u
|
|
|
10
11
|
import { parseRunCommand, resolveResultsPatterns } from "../utils/resultsPatterns.js";
|
|
11
12
|
import { executeAllureRun, executeNestedAllureCommand } from "./commons/run.js";
|
|
12
13
|
const missingRunCommandUsageError = () => new UsageError("expecting command to be specified after --, e.g. allure run -- npm run test");
|
|
14
|
+
const runLogger = new Logger("AllureRun");
|
|
13
15
|
export class RunCommand extends Command {
|
|
14
16
|
constructor() {
|
|
15
17
|
super(...arguments);
|
|
@@ -32,7 +34,7 @@ export class RunCommand extends Command {
|
|
|
32
34
|
description: "The report name (default: Allure Report)",
|
|
33
35
|
});
|
|
34
36
|
this.rerun = Option.String("--rerun", {
|
|
35
|
-
description: "The number of reruns for failed tests
|
|
37
|
+
description: "The maximum number of reruns for failed tests and Quality Gate fast-fails (default: 0)",
|
|
36
38
|
});
|
|
37
39
|
this.silent = Option.Boolean("--silent", {
|
|
38
40
|
description: "Don't pipe the process output logs to console (default: 0)",
|
|
@@ -74,14 +76,10 @@ export class RunCommand extends Command {
|
|
|
74
76
|
throw missingRunCommandUsageError();
|
|
75
77
|
}
|
|
76
78
|
const before = new Date().getTime();
|
|
77
|
-
process.on("exit", (exitCode) => {
|
|
78
|
-
const after = new Date().getTime();
|
|
79
|
-
console.log(`exit code ${exitCode} (${after - before}ms)`);
|
|
80
|
-
});
|
|
81
79
|
const cwd = await realpath(this.cwd ?? process.cwd());
|
|
82
80
|
const hideLabels = this.hideLabels?.length ? this.hideLabels : undefined;
|
|
83
|
-
console.log(`${command} ${commandArgs.join(" ")}`);
|
|
84
81
|
if (getActiveAllureCliCommand()) {
|
|
82
|
+
runLogger.info(`Running nested command: ${[command, ...commandArgs].join(" ")}`);
|
|
85
83
|
const exitCode = await executeNestedAllureCommand({
|
|
86
84
|
command,
|
|
87
85
|
commandArgs,
|
|
@@ -109,17 +107,13 @@ export class RunCommand extends Command {
|
|
|
109
107
|
});
|
|
110
108
|
const resultsPatterns = resolveResultsPatterns(this.resultsDir ?? [], config.resultsDir);
|
|
111
109
|
const resolvedEnvironment = resolveCommandEnvironment(config, environmentOptions);
|
|
112
|
-
const
|
|
113
|
-
const withQualityGate = !!config.qualityGate && !withRerun;
|
|
114
|
-
if (config.qualityGate && withRerun) {
|
|
115
|
-
console.warn("Quality gate doesn't work with rerun; skipping quality gate validation.");
|
|
116
|
-
}
|
|
110
|
+
const withQualityGate = !!config.qualityGate;
|
|
117
111
|
try {
|
|
118
112
|
await rm(config.output, { recursive: true });
|
|
119
113
|
}
|
|
120
114
|
catch (e) {
|
|
121
115
|
if (!isFileNotFoundError(e)) {
|
|
122
|
-
|
|
116
|
+
runLogger.error(`Could not clean output directory: ${e instanceof Error ? e.message : String(e)}`);
|
|
123
117
|
}
|
|
124
118
|
}
|
|
125
119
|
const allureReport = new AllureReport({
|
|
@@ -157,6 +151,8 @@ export class RunCommand extends Command {
|
|
|
157
151
|
maxRerun,
|
|
158
152
|
resultsPatterns,
|
|
159
153
|
});
|
|
154
|
+
const finalExitCode = globalExitCode.actual ?? globalExitCode.original;
|
|
155
|
+
runLogger.info(`Completed with exit code ${finalExitCode} after ${formatDuration(Date.now() - before)}`);
|
|
160
156
|
if (config.open) {
|
|
161
157
|
await serve({
|
|
162
158
|
port: config.port ? parseInt(config.port, 10) : undefined,
|
|
@@ -165,7 +161,7 @@ export class RunCommand extends Command {
|
|
|
165
161
|
});
|
|
166
162
|
}
|
|
167
163
|
else {
|
|
168
|
-
exit(
|
|
164
|
+
exit(finalExitCode);
|
|
169
165
|
}
|
|
170
166
|
}
|
|
171
167
|
}
|
package/dist/rules.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, metricMaxRule, metricMinRule, metricMaxDeltaRule, metricMaxDeltaPercentRule, } from "@allurereport/core";
|
|
1
|
+
export { qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, newTestsRule, successRateRule, metricMaxRule, metricMinRule, metricMaxDeltaRule, metricMaxDeltaPercentRule, } from "@allurereport/core";
|
|
2
2
|
export type { QualityGateRule, QualityGateRuleState, QualityGateValidationResult } from "@allurereport/plugin-api";
|
package/dist/rules.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, successRateRule, metricMaxRule, metricMinRule, metricMaxDeltaRule, metricMaxDeltaPercentRule, } from "@allurereport/core";
|
|
1
|
+
export { qualityGateDefaultRules, maxFailuresRule, minTestsCountRule, newTestsRule, successRateRule, metricMaxRule, metricMinRule, metricMaxDeltaRule, metricMaxDeltaPercentRule, } from "@allurereport/core";
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
package/dist/utils/logs.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const getLogFileName: () => string;
|
|
2
2
|
export declare const readLogs: () => Promise<string>;
|
|
3
|
-
export declare const logError: (message: string, error: Error) => Promise<void>;
|
|
3
|
+
export declare const logError: (message: string, error: Error, log?: (formattedMessage: string) => void) => Promise<void>;
|
package/dist/utils/logs.js
CHANGED
|
@@ -11,11 +11,13 @@ export const readLogs = async () => {
|
|
|
11
11
|
const logs = await readFile(logFilePath, "utf-8");
|
|
12
12
|
return logs;
|
|
13
13
|
}
|
|
14
|
-
catch
|
|
14
|
+
catch {
|
|
15
15
|
return "";
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
|
-
export const logError = async (message, error) => {
|
|
18
|
+
export const logError = async (message, error, log = (formattedMessage) => {
|
|
19
|
+
console.error(red(formattedMessage));
|
|
20
|
+
}) => {
|
|
19
21
|
let logs = await readLogs();
|
|
20
22
|
if (!logs) {
|
|
21
23
|
logs += `${new Date().toISOString()}[ERROR] ${error.message}\n`;
|
|
@@ -28,5 +30,5 @@ export const logError = async (message, error) => {
|
|
|
28
30
|
}
|
|
29
31
|
const logFilePath = join(LOGS_DIRECTORY, getLogFileName());
|
|
30
32
|
await writeFile(logFilePath, logs, "utf-8");
|
|
31
|
-
|
|
33
|
+
log(`${message}. Check logs for more details: ${logFilePath}`);
|
|
32
34
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "allure",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.20.0",
|
|
4
4
|
"description": "Allure Commandline Tool",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"allure",
|
|
@@ -32,26 +32,27 @@
|
|
|
32
32
|
"lint:fix": "oxlint --import-plugin --fix src test features stories"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@allurereport/charts-api": "3.
|
|
36
|
-
"@allurereport/ci": "3.
|
|
37
|
-
"@allurereport/
|
|
38
|
-
"@allurereport/core
|
|
39
|
-
"@allurereport/
|
|
40
|
-
"@allurereport/
|
|
41
|
-
"@allurereport/plugin-
|
|
42
|
-
"@allurereport/plugin-
|
|
43
|
-
"@allurereport/plugin-
|
|
44
|
-
"@allurereport/plugin-
|
|
45
|
-
"@allurereport/plugin-
|
|
46
|
-
"@allurereport/plugin-
|
|
47
|
-
"@allurereport/plugin-
|
|
48
|
-
"@allurereport/plugin-
|
|
49
|
-
"@allurereport/plugin-
|
|
50
|
-
"@allurereport/plugin-
|
|
51
|
-
"@allurereport/plugin-
|
|
52
|
-
"@allurereport/
|
|
53
|
-
"@allurereport/
|
|
54
|
-
"@allurereport/
|
|
35
|
+
"@allurereport/charts-api": "3.20.0",
|
|
36
|
+
"@allurereport/ci": "3.20.0",
|
|
37
|
+
"@allurereport/cli-commons": "3.20.0",
|
|
38
|
+
"@allurereport/core": "3.20.0",
|
|
39
|
+
"@allurereport/core-api": "3.20.0",
|
|
40
|
+
"@allurereport/directory-watcher": "3.20.0",
|
|
41
|
+
"@allurereport/plugin-agent": "3.20.0",
|
|
42
|
+
"@allurereport/plugin-allure2": "3.20.0",
|
|
43
|
+
"@allurereport/plugin-api": "3.20.0",
|
|
44
|
+
"@allurereport/plugin-awesome": "3.20.0",
|
|
45
|
+
"@allurereport/plugin-classic": "3.20.0",
|
|
46
|
+
"@allurereport/plugin-csv": "3.20.0",
|
|
47
|
+
"@allurereport/plugin-dashboard": "3.20.0",
|
|
48
|
+
"@allurereport/plugin-jira": "3.20.0",
|
|
49
|
+
"@allurereport/plugin-log": "3.20.0",
|
|
50
|
+
"@allurereport/plugin-progress": "3.20.0",
|
|
51
|
+
"@allurereport/plugin-server-reload": "3.20.0",
|
|
52
|
+
"@allurereport/plugin-slack": "3.20.0",
|
|
53
|
+
"@allurereport/reader-api": "3.20.0",
|
|
54
|
+
"@allurereport/service": "3.20.0",
|
|
55
|
+
"@allurereport/static-server": "3.20.0",
|
|
55
56
|
"adm-zip": "^0.6.0",
|
|
56
57
|
"clipanion": "^4.0.0-rc.4",
|
|
57
58
|
"glob": "^13.0.6",
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
"@types/node": "^20",
|
|
67
68
|
"@types/prompts": "^2",
|
|
68
69
|
"@vitest/runner": "^2",
|
|
69
|
-
"@vitest/snapshot": "^
|
|
70
|
+
"@vitest/snapshot": "^5.0.1",
|
|
70
71
|
"allure-js-commons": "^3",
|
|
71
72
|
"allure-vitest": "^3",
|
|
72
73
|
"rimraf": "^6",
|
package/dist/utils/terminal.d.ts
DELETED
package/dist/utils/terminal.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import console from "node:console";
|
|
2
|
-
import { formatDuration } from "@allurereport/core-api";
|
|
3
|
-
import { blue, gray, green, red, yellow } from "yoctocolors";
|
|
4
|
-
export const status2color = (status) => {
|
|
5
|
-
switch (status) {
|
|
6
|
-
case "failed":
|
|
7
|
-
return red;
|
|
8
|
-
case "broken":
|
|
9
|
-
return yellow;
|
|
10
|
-
case "passed":
|
|
11
|
-
return green;
|
|
12
|
-
case "skipped":
|
|
13
|
-
return gray;
|
|
14
|
-
default:
|
|
15
|
-
return blue;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
export const logTests = (testResults) => {
|
|
19
|
-
testResults.forEach((tr) => console.log(`${tr.fullName} ${status2color(tr.status)(tr.status)} ${tr.duration !== undefined ? gray(`(${formatDuration(tr.duration)})`) : ""}`));
|
|
20
|
-
};
|