allure 3.17.0 → 3.19.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.
@@ -10,6 +10,7 @@ export declare class Allure2Command extends Command {
10
10
  reportLanguage: string | undefined;
11
11
  singleFile: boolean | undefined;
12
12
  historyPath: string | undefined;
13
+ historyBaseUrl: string | undefined;
13
14
  knownIssues: string | undefined;
14
15
  execute(): Promise<void>;
15
16
  }
@@ -33,6 +33,9 @@ export class Allure2Command extends Command {
33
33
  this.historyPath = Option.String("--history-path,-h", {
34
34
  description: "The path to history file",
35
35
  });
36
+ this.historyBaseUrl = Option.String("--history-base-url", {
37
+ description: "The public base URL of the generated report directory",
38
+ });
36
39
  this.knownIssues = Option.String("--known-issues", {
37
40
  description: "Path to known issues file",
38
41
  });
@@ -49,6 +52,7 @@ export class Allure2Command extends Command {
49
52
  name: this.reportName,
50
53
  resolutions: { knownIssuesPath: this.knownIssues },
51
54
  historyPath: this.historyPath,
55
+ ...(this.historyBaseUrl !== undefined ? { historyBaseUrl: this.historyBaseUrl } : {}),
52
56
  });
53
57
  const { resultDirectories, patterns } = await resolveAndFindResultsDirs(cwd, this.resultsDir, config.resultsDir);
54
58
  if (!resultDirectories.length) {
@@ -12,6 +12,7 @@ export declare class AwesomeCommand extends Command {
12
12
  theme: string | undefined;
13
13
  reportLanguage: string | undefined;
14
14
  historyPath: string | undefined;
15
+ historyBaseUrl: string | undefined;
15
16
  knownIssues: string | undefined;
16
17
  groupBy: string | undefined;
17
18
  hideLabels: string[] | undefined;
@@ -39,6 +39,9 @@ export class AwesomeCommand extends Command {
39
39
  this.historyPath = Option.String("--history-path,-h", {
40
40
  description: "The path to history file",
41
41
  });
42
+ this.historyBaseUrl = Option.String("--history-base-url", {
43
+ description: "The public base URL of the generated report directory",
44
+ });
42
45
  this.knownIssues = Option.String("--known-issues", {
43
46
  description: "Path to known issues file",
44
47
  });
@@ -58,6 +61,7 @@ export class AwesomeCommand extends Command {
58
61
  name: this.reportName,
59
62
  resolutions: { knownIssuesPath: this.knownIssues },
60
63
  historyPath: this.historyPath,
64
+ ...(this.historyBaseUrl !== undefined ? { historyBaseUrl: this.historyBaseUrl } : {}),
61
65
  hideLabels,
62
66
  });
63
67
  const { resultDirectories, patterns } = await resolveAndFindResultsDirs(cwd, this.resultsDir, config.resultsDir);
@@ -10,6 +10,7 @@ export declare class ClassicCommand extends Command {
10
10
  reportLanguage: string | undefined;
11
11
  singleFile: boolean | undefined;
12
12
  historyPath: string | undefined;
13
+ historyBaseUrl: string | undefined;
13
14
  knownIssues: string | undefined;
14
15
  execute(): Promise<void>;
15
16
  }
@@ -33,6 +33,9 @@ export class ClassicCommand extends Command {
33
33
  this.historyPath = Option.String("--history-path,-h", {
34
34
  description: "The path to history file",
35
35
  });
36
+ this.historyBaseUrl = Option.String("--history-base-url", {
37
+ description: "The public base URL of the generated report directory",
38
+ });
36
39
  this.knownIssues = Option.String("--known-issues", {
37
40
  description: "Path to known issues file",
38
41
  });
@@ -49,6 +52,7 @@ export class ClassicCommand extends Command {
49
52
  name: this.reportName,
50
53
  resolutions: { knownIssuesPath: this.knownIssues },
51
54
  historyPath: this.historyPath,
55
+ ...(this.historyBaseUrl !== undefined ? { historyBaseUrl: this.historyBaseUrl } : {}),
52
56
  });
53
57
  const { resultDirectories, patterns } = await resolveAndFindResultsDirs(cwd, this.resultsDir, config.resultsDir);
54
58
  if (!resultDirectories.length) {
@@ -1,7 +1,12 @@
1
+ import { type ReportContext } from "@allurereport/ci";
1
2
  import type { FullConfig } from "@allurereport/core";
3
+ export type GenerateResult = {
4
+ summary?: ReportContext;
5
+ };
2
6
  export declare const generate: (params: {
3
7
  cwd: string;
4
8
  config: FullConfig;
5
9
  resultsDir?: string[];
6
10
  dump?: string[];
7
- }) => Promise<void>;
11
+ collectSummary?: boolean;
12
+ }) => Promise<GenerateResult | undefined>;
@@ -1,4 +1,5 @@
1
1
  import { exit } from "node:process";
2
+ import { createReportContext } from "@allurereport/ci";
2
3
  import { AllureReport } from "@allurereport/core";
3
4
  import { KnownError } from "@allurereport/service";
4
5
  import { red } from "yoctocolors";
@@ -26,6 +27,23 @@ export const generate = async (params) => {
26
27
  await allureReport.readDirectory(dir);
27
28
  }
28
29
  await allureReport.done();
30
+ if (!params.collectSummary) {
31
+ return;
32
+ }
33
+ const result = {};
34
+ try {
35
+ const summary = await createReportContext(params.config.output, {
36
+ onError: (message) => console.warn(message),
37
+ });
38
+ if (!summary.testResults && summary.reports.length === 0) {
39
+ throw new Error("no report context data found");
40
+ }
41
+ result.summary = summary;
42
+ }
43
+ catch (error) {
44
+ console.warn(`Report summary skipped: ${error instanceof Error ? error.message : String(error)}`);
45
+ }
46
+ return result;
29
47
  }
30
48
  catch (error) {
31
49
  if (error instanceof KnownError) {
@@ -4,7 +4,7 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join, resolve } from "node:path";
6
6
  import process from "node:process";
7
- import { QualityGateState, stringifyQualityGateResults } from "@allurereport/core";
7
+ import { QualityGateState, filterFailedQualityGateResults, stringifyQualityGateResults, } from "@allurereport/core";
8
8
  import { createTestPlan } from "@allurereport/core-api";
9
9
  import { allureResultsDirectoriesWatcher, delayedFileProcessingWatcher, newFilesInDirectoryWatcher, } from "@allurereport/directory-watcher";
10
10
  import { formatProcessLogAttachmentName } from "@allurereport/plugin-agent";
@@ -159,6 +159,27 @@ export const runTests = async (params) => {
159
159
  fastFailed: fastFailTriggered,
160
160
  };
161
161
  };
162
+ const publishProcessGlobals = (params) => {
163
+ const { allureReport, command, commandArgs, ignoreLogs, processFailed, testProcessResult } = params;
164
+ if (!ignoreLogs && testProcessResult?.stdout) {
165
+ const fileName = randomUUID();
166
+ const stdoutResultFile = new BufferResultFile(Buffer.from(testProcessResult.stdout, "utf8"), `${fileName}`);
167
+ stdoutResultFile.contentType = "text/plain";
168
+ allureReport.realtimeDispatcher.sendProcessGlobalAttachment(stdoutResultFile, formatProcessLogAttachmentName([command, ...commandArgs].join(" "), "stdout"));
169
+ }
170
+ if (!ignoreLogs && testProcessResult?.stderr) {
171
+ const fileName = randomUUID();
172
+ const stderrResultFile = new BufferResultFile(Buffer.from(testProcessResult.stderr, "utf8"), fileName);
173
+ stderrResultFile.contentType = "text/plain";
174
+ allureReport.realtimeDispatcher.sendProcessGlobalAttachment(stderrResultFile, formatProcessLogAttachmentName([command, ...commandArgs].join(" "), "stderr"));
175
+ if (processFailed) {
176
+ allureReport.realtimeDispatcher.sendProcessGlobalError({
177
+ message: "Test process has failed",
178
+ trace: testProcessResult.stderr,
179
+ });
180
+ }
181
+ }
182
+ };
162
183
  export const executeAllureRun = async (params) => {
163
184
  const { allureReport, cwd, command, commandArgs, environmentVariables = {}, environment, withQualityGate, silent, logs, ignoreLogs, maxRerun = 0, logProcessExit = true, resultsPatterns = [], } = params;
164
185
  await allureReport.start();
@@ -187,6 +208,16 @@ export const executeAllureRun = async (params) => {
187
208
  if (allFailuresAfterInitialRun.length > 0 && blockingFailuresAfterInitialRun.length === 0 && testProcessResult) {
188
209
  globalExitCode.actual = 0;
189
210
  }
211
+ if (maxRerun > 0) {
212
+ publishProcessGlobals({
213
+ allureReport,
214
+ command,
215
+ commandArgs,
216
+ ignoreLogs,
217
+ processFailed: Math.abs(globalExitCode.actual ?? testProcessResult?.code ?? -1) !== 0,
218
+ testProcessResult,
219
+ });
220
+ }
190
221
  for (let rerun = 0; rerun < maxRerun && testProcessResult && !testProcessResult.fastFailed; rerun++) {
191
222
  const failed = await allureReport.store.blockingFailedTestResults();
192
223
  if (failed.length === 0) {
@@ -199,6 +230,7 @@ export const executeAllureRun = async (params) => {
199
230
  const tmpDir = await mkdtemp(join(tmpdir(), "allure-run-"));
200
231
  const testPlanPath = resolve(tmpDir, `${rerun}-testplan.json`);
201
232
  await writeFile(testPlanPath, JSON.stringify(testPlan));
233
+ allureReport.realtimeDispatcher.sendProcessGlobalsReset();
202
234
  testProcessResult = await runTests({
203
235
  silent,
204
236
  logs,
@@ -217,6 +249,18 @@ export const executeAllureRun = async (params) => {
217
249
  resultsPatterns,
218
250
  });
219
251
  await rm(tmpDir, { recursive: true });
252
+ const allFailuresAfterRerun = await allureReport.store.failedTestResults();
253
+ const blockingFailuresAfterRerun = await allureReport.store.blockingFailedTestResults();
254
+ publishProcessGlobals({
255
+ allureReport,
256
+ command,
257
+ commandArgs,
258
+ ignoreLogs,
259
+ processFailed: Math.abs(allFailuresAfterRerun.length > 0 && blockingFailuresAfterRerun.length === 0
260
+ ? 0
261
+ : (testProcessResult?.code ?? -1)) !== 0,
262
+ testProcessResult,
263
+ });
220
264
  logTests(await allureReport.store.allTestResults());
221
265
  }
222
266
  const allFailuresAfterReruns = await allureReport.store.failedTestResults();
@@ -235,12 +279,14 @@ export const executeAllureRun = async (params) => {
235
279
  }
236
280
  if (qualityGateResults.length) {
237
281
  const qualityGateMessage = stringifyQualityGateResults(qualityGateResults);
238
- console.error(qualityGateMessage);
282
+ if (qualityGateMessage) {
283
+ console.error(qualityGateMessage);
284
+ }
239
285
  allureReport.realtimeDispatcher.sendQualityGateResults(qualityGateResults);
240
286
  }
241
287
  globalExitCode.original = testProcessResult?.code ?? -1;
242
288
  if (withQualityGate) {
243
- globalExitCode.actual = qualityGateResults.length > 0 ? 1 : 0;
289
+ globalExitCode.actual = filterFailedQualityGateResults(qualityGateResults).length > 0 ? 1 : 0;
244
290
  }
245
291
  }
246
292
  catch (error) {
@@ -260,23 +306,15 @@ export const executeAllureRun = async (params) => {
260
306
  }
261
307
  }
262
308
  const processFailed = Math.abs(globalExitCode.actual ?? globalExitCode.original) !== 0;
263
- if (!ignoreLogs && testProcessResult?.stdout) {
264
- const fileName = randomUUID();
265
- const stdoutResultFile = new BufferResultFile(Buffer.from(testProcessResult.stdout, "utf8"), `${fileName}`);
266
- stdoutResultFile.contentType = "text/plain";
267
- allureReport.realtimeDispatcher.sendGlobalAttachment(stdoutResultFile, formatProcessLogAttachmentName([command, ...commandArgs].join(" "), "stdout"));
268
- }
269
- if (!ignoreLogs && testProcessResult?.stderr) {
270
- const fileName = randomUUID();
271
- const stderrResultFile = new BufferResultFile(Buffer.from(testProcessResult.stderr, "utf8"), fileName);
272
- stderrResultFile.contentType = "text/plain";
273
- allureReport.realtimeDispatcher.sendGlobalAttachment(stderrResultFile, formatProcessLogAttachmentName([command, ...commandArgs].join(" "), "stderr"));
274
- if (processFailed) {
275
- allureReport.realtimeDispatcher.sendGlobalError({
276
- message: "Test process has failed",
277
- trace: testProcessResult.stderr,
278
- });
279
- }
309
+ if (maxRerun === 0) {
310
+ publishProcessGlobals({
311
+ allureReport,
312
+ command,
313
+ commandArgs,
314
+ ignoreLogs,
315
+ processFailed,
316
+ testProcessResult,
317
+ });
280
318
  }
281
319
  allureReport.realtimeDispatcher.sendGlobalExitCode(globalExitCode);
282
320
  await allureReport.done();
@@ -11,6 +11,7 @@ export declare class GenerateCommand extends Command {
11
11
  open: boolean | undefined;
12
12
  port: string | undefined;
13
13
  historyLimit: string | undefined;
14
+ historyBaseUrl: string | undefined;
14
15
  hideLabels: string[] | undefined;
15
16
  knownIssues: string | undefined;
16
17
  execute(): Promise<void>;
@@ -35,6 +35,9 @@ export class GenerateCommand extends Command {
35
35
  this.historyLimit = Option.String("--history-limit", {
36
36
  description: "Limits the number of history entries to keep (default: unlimited)",
37
37
  });
38
+ this.historyBaseUrl = Option.String("--history-base-url", {
39
+ description: "The public base URL of the generated report directory",
40
+ });
38
41
  this.hideLabels = Option.Array("--hide-labels", {
39
42
  description: "Hide labels by exact name in generated reports. Repeat the option for multiple labels",
40
43
  });
@@ -53,6 +56,7 @@ export class GenerateCommand extends Command {
53
56
  port: this.port,
54
57
  hideLabels,
55
58
  historyLimit: this.historyLimit !== undefined ? parseInt(this.historyLimit, 10) : undefined,
59
+ ...(this.historyBaseUrl !== undefined ? { historyBaseUrl: this.historyBaseUrl } : {}),
56
60
  resolutions: { knownIssuesPath: this.knownIssues },
57
61
  });
58
62
  await generate({
@@ -0,0 +1,15 @@
1
+ import { Command } from "clipanion";
2
+ export declare class GitlabGenerateCommand extends Command {
3
+ static paths: string[][];
4
+ static usage: import("clipanion").Usage;
5
+ resultsDir: string[];
6
+ config: string | undefined;
7
+ output: string | undefined;
8
+ reportName: string | undefined;
9
+ dump: string[] | undefined;
10
+ historyPath: string | undefined;
11
+ historyLimit: string | undefined;
12
+ historyBaseUrl: string | undefined;
13
+ gitlabToken: string | undefined;
14
+ execute(): Promise<void>;
15
+ }
@@ -0,0 +1,137 @@
1
+ import { existsSync } from "node:fs";
2
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
+ import { cwd as processCwd } from "node:process";
4
+ import { restoreGitlabHistory, upsertGitlabJobNote, detect } from "@allurereport/ci";
5
+ import { readConfig } from "@allurereport/core";
6
+ import { CiType } from "@allurereport/core-api";
7
+ import { Command, Option } from "clipanion";
8
+ import { generate } from "../commons/generate.js";
9
+ const DEFAULT_HISTORY_LIMIT = 100;
10
+ const isDecimalString = (value) => /^[0-9]+$/.test(value);
11
+ const parseHistoryLimit = (value) => {
12
+ if (typeof value === "string") {
13
+ if (!isDecimalString(value)) {
14
+ return DEFAULT_HISTORY_LIMIT;
15
+ }
16
+ const parsed = Number(value);
17
+ return Number.isFinite(parsed) && Number.isInteger(parsed) && parsed >= 0 ? parsed : DEFAULT_HISTORY_LIMIT;
18
+ }
19
+ if (typeof value === "number") {
20
+ return Number.isFinite(value) && Number.isInteger(value) && value >= 0 ? value : DEFAULT_HISTORY_LIMIT;
21
+ }
22
+ return DEFAULT_HISTORY_LIMIT;
23
+ };
24
+ const reportBaseUrl = (historyBaseUrl, output) => {
25
+ const gitlab = detect();
26
+ const isGitlabCiDescriptor = (ci) => ci.type === CiType.Gitlab;
27
+ if (!isGitlabCiDescriptor(gitlab)) {
28
+ throw new Error("GitLab CI environment was not detected");
29
+ }
30
+ if (isAbsolute(output)) {
31
+ const projectDirectory = resolve(gitlab.projectDirectory);
32
+ const absoluteOutput = resolve(output);
33
+ if (absoluteOutput !== projectDirectory && !absoluteOutput.startsWith(join(projectDirectory, sep))) {
34
+ throw new Error("Absolute output path must be within CI_PROJECT_DIR");
35
+ }
36
+ output = relative(projectDirectory, absoluteOutput).split(sep).join("/");
37
+ }
38
+ return historyBaseUrl || `${gitlab.jobArtifactsUrlBase}/${output}`;
39
+ };
40
+ export class GitlabGenerateCommand extends Command {
41
+ constructor() {
42
+ super(...arguments);
43
+ this.resultsDir = Option.Rest({
44
+ name: "Patterns to match test results directories. Overrides config.resultsDir. Defaults to ./**/allure-results when neither is set.",
45
+ });
46
+ this.config = Option.String("--config,-c", {
47
+ description: "The path to Allure config file",
48
+ });
49
+ this.output = Option.String("--output,-o", {
50
+ description: "The output directory name. Absolute paths are accepted as well (default: allure-report)",
51
+ });
52
+ this.reportName = Option.String("--report-name,--name", {
53
+ description: "The report name (default: Allure Report)",
54
+ });
55
+ this.dump = Option.Array("--dump", {
56
+ description: "Path or pattern that matches one or more archives created by `allure run --dump ...`. " +
57
+ "Allure loads the matched archives before generating the report. " +
58
+ "This option can be specified multiple times.",
59
+ });
60
+ this.historyPath = Option.String("--history-path", {
61
+ description: "History file path",
62
+ });
63
+ this.historyLimit = Option.String("--history-limit", {
64
+ description: "Limits the number of history entries to keep (default: 100)",
65
+ });
66
+ this.historyBaseUrl = Option.String("--history-base-url", {
67
+ description: "The public base URL of the generated report directory",
68
+ });
69
+ this.gitlabToken = Option.String("--gitlab-token", {
70
+ description: "GitLab api token with api write permissions",
71
+ });
72
+ }
73
+ async execute() {
74
+ const cwd = processCwd();
75
+ const token = this.gitlabToken?.trim() || process.env.GITLAB_TOKEN?.trim() || undefined;
76
+ const output = this.output ?? "allure-report";
77
+ const configPath = this.config && existsSync(this.config) ? this.config : undefined;
78
+ const historyPath = this.historyPath === undefined ? "history.jsonl" : this.historyPath;
79
+ const config = await readConfig(cwd, configPath, {
80
+ output,
81
+ name: this.reportName,
82
+ historyPath: historyPath,
83
+ historyBaseUrl: reportBaseUrl(this.historyBaseUrl, output),
84
+ historyLimit: parseHistoryLimit(this.historyLimit),
85
+ });
86
+ const runGitlabOperation = async (errPrefix, operation) => {
87
+ try {
88
+ await operation();
89
+ }
90
+ catch (error) {
91
+ console.error(`${errPrefix}: ${error instanceof Error ? error.message : String(error)}`);
92
+ }
93
+ };
94
+ console.log("Generating allure report");
95
+ if (!config.allureService?.accessToken) {
96
+ console.log(" fetching previous run history");
97
+ await runGitlabOperation(" history fetch failed", () => restoreGitlabHistory({
98
+ token,
99
+ historyPath,
100
+ }));
101
+ }
102
+ const result = await generate({
103
+ cwd,
104
+ config,
105
+ dump: this.dump,
106
+ resultsDir: this.resultsDir,
107
+ collectSummary: true,
108
+ });
109
+ if (!result) {
110
+ return;
111
+ }
112
+ const reportUrl = `${config.historyBaseUrl}/index.html`;
113
+ console.log(`GitLab report URL: ${reportUrl}`);
114
+ if (result.summary && existsSync(join(config.output, "index.html"))) {
115
+ console.log("Posting report summary comment");
116
+ if (!token) {
117
+ console.log(" no API token provided, skipping");
118
+ return;
119
+ }
120
+ const { summary } = result;
121
+ await runGitlabOperation(" failed to add summary comment", () => upsertGitlabJobNote({
122
+ token,
123
+ summary,
124
+ reportUrl,
125
+ }));
126
+ }
127
+ }
128
+ }
129
+ GitlabGenerateCommand.paths = [["gitlab"]];
130
+ GitlabGenerateCommand.usage = Command.Usage({
131
+ category: "Integrations",
132
+ description: "Generate test report and post report summary in merge request comments",
133
+ details: "This command generates a report from the provided Allure Results directories. When api access token is configured, " +
134
+ "integration will post summary as comment for merge request pipelines and attempt to lookup history file from previously executed job ." +
135
+ "This integration is designed to be executed from within GitLab CI job.",
136
+ examples: [["gitlab ./allure-results", "Generate a report from the ./allure-results directory"]],
137
+ });
@@ -0,0 +1 @@
1
+ export * from "./generate.js";
@@ -0,0 +1 @@
1
+ export * from "./generate.js";
@@ -5,6 +5,7 @@ export declare class HistoryCommand extends Command {
5
5
  resultsDir: string[];
6
6
  historyPath: string | undefined;
7
7
  historyLimit: string | undefined;
8
+ historyBaseUrl: string | undefined;
8
9
  reportName: string | undefined;
9
10
  execute(): Promise<void>;
10
11
  }
@@ -16,6 +16,9 @@ export class HistoryCommand extends Command {
16
16
  this.historyLimit = Option.String("--history-limit", {
17
17
  description: "Limits the number of history entries to keep (default: unlimited)",
18
18
  });
19
+ this.historyBaseUrl = Option.String("--history-base-url", {
20
+ description: "The public base URL of the generated report directory",
21
+ });
19
22
  this.reportName = Option.String("--report-name,--name", {
20
23
  description: "The report name",
21
24
  });
@@ -31,10 +34,11 @@ export class HistoryCommand extends Command {
31
34
  }
32
35
  const config = await resolveConfig({
33
36
  historyPath: this.historyPath ?? "history.jsonl",
37
+ historyBaseUrl: this.historyBaseUrl ?? rawConfig.historyBaseUrl,
34
38
  historyLimit: this.historyLimit ? Number(this.historyLimit) : undefined,
35
39
  name: this.reportName ?? "Allure Report",
36
40
  plugins: {},
37
- });
41
+ }, { plugins: {} });
38
42
  const allureReport = new AllureReport(config);
39
43
  await allureReport.start();
40
44
  for (const directory of resultDirectories) {
@@ -19,3 +19,4 @@ export * from "./generate.js";
19
19
  export * from "./dashboard.js";
20
20
  export * from "./results/index.js";
21
21
  export * from "./jira/index.js";
22
+ export * from "./gitlab/index.js";
@@ -19,3 +19,4 @@ export * from "./generate.js";
19
19
  export * from "./dashboard.js";
20
20
  export * from "./results/index.js";
21
21
  export * from "./jira/index.js";
22
+ export * from "./gitlab/index.js";
@@ -8,5 +8,7 @@ export declare class LogCommand extends Command {
8
8
  groupBy: string | undefined;
9
9
  allSteps: boolean | undefined;
10
10
  withTrace: boolean | undefined;
11
+ qualityGateResults: boolean;
12
+ allQualityGateResults: boolean;
11
13
  execute(): Promise<void>;
12
14
  }
@@ -5,6 +5,7 @@ import { AllureReport, readConfig } from "@allurereport/core";
5
5
  import LogPlugin from "@allurereport/plugin-log";
6
6
  import { Command, Option } from "clipanion";
7
7
  import { red } from "yoctocolors";
8
+ import { resolveCommandEnvironment } from "../utils/environment.js";
8
9
  import { resolveAndFindResultsDirs } from "../utils/resultsPatterns.js";
9
10
  export class LogCommand extends Command {
10
11
  constructor() {
@@ -27,6 +28,12 @@ export class LogCommand extends Command {
27
28
  this.withTrace = Option.Boolean("--with-trace", {
28
29
  description: "Print stack trace for failed tests",
29
30
  });
31
+ this.qualityGateResults = Option.Boolean("--quality-gate-results", true, {
32
+ description: "Print quality gate validation results after tests (default: true). Pass --no-quality-gate-results to hide them",
33
+ });
34
+ this.allQualityGateResults = Option.Boolean("--all-quality-gate-results", false, {
35
+ description: "Include successful quality gate validation results in addition to failures",
36
+ });
30
37
  }
31
38
  async execute() {
32
39
  const cwd = await realpath(this.cwd ?? process.cwd());
@@ -35,8 +42,11 @@ export class LogCommand extends Command {
35
42
  allSteps: this.allSteps ?? false,
36
43
  withTrace: this.withTrace ?? false,
37
44
  groupBy: this.groupBy ?? "suite",
45
+ qualityGateResults: this.qualityGateResults,
46
+ qualityGateFilter: this.allQualityGateResults ? () => true : undefined,
38
47
  };
39
48
  const config = await readConfig(cwd, this.config);
49
+ const resolvedEnvironment = resolveCommandEnvironment(config, {});
40
50
  const { resultDirectories, patterns } = await resolveAndFindResultsDirs(cwd, this.resultsDir, config.resultsDir);
41
51
  if (!resultDirectories.length) {
42
52
  console.error(red(`No test results directories found matching pattern: ${patterns}`));
@@ -51,11 +61,21 @@ export class LogCommand extends Command {
51
61
  plugin: new LogPlugin(defaultLogOptions),
52
62
  },
53
63
  ];
54
- const allureReport = new AllureReport(config);
64
+ const allureReport = new AllureReport({
65
+ ...config,
66
+ environment: resolvedEnvironment?.id,
67
+ });
55
68
  await allureReport.start();
56
69
  for (const directory of resultDirectories) {
57
70
  await allureReport.readDirectory(directory);
58
71
  }
72
+ if (this.qualityGateResults && allureReport.hasQualityGate) {
73
+ const allTrs = await allureReport.store.allTestResults({ includeRetries: false });
74
+ const validationResults = await allureReport.validate({ trs: allTrs, environment: resolvedEnvironment?.id });
75
+ if (validationResults.results.length > 0) {
76
+ allureReport.realtimeDispatcher.sendQualityGateResults(validationResults.results);
77
+ }
78
+ }
59
79
  await allureReport.done();
60
80
  const after = new Date().getTime();
61
81
  console.log(`the report successfully generated (${after - before}ms)`);
@@ -1,7 +1,7 @@
1
1
  import * as console from "node:console";
2
2
  import { realpath } from "node:fs/promises";
3
3
  import { exit, cwd as processCwd } from "node:process";
4
- import { AllureReport, QualityGateState, readConfig, stringifyQualityGateResults } from "@allurereport/core";
4
+ import { AllureReport, QualityGateState, filterFailedQualityGateResults, readConfig, stringifyQualityGateResults, } from "@allurereport/core";
5
5
  import { Command, Option } from "clipanion";
6
6
  import * as typanion from "typanion";
7
7
  import { red } from "yoctocolors";
@@ -112,7 +112,7 @@ export class QualityGateCommand extends Command {
112
112
  trs: allTrs,
113
113
  environment: resolvedEnvironment?.id,
114
114
  });
115
- if (validationResults.results.length === 0) {
115
+ if (filterFailedQualityGateResults(validationResults.results).length === 0) {
116
116
  exit(0);
117
117
  return;
118
118
  }
@@ -15,6 +15,7 @@ export declare class RunCommand extends Command {
15
15
  environment: string | undefined;
16
16
  environmentName: string | undefined;
17
17
  historyLimit: string | undefined;
18
+ historyBaseUrl: string | undefined;
18
19
  hideLabels: string[] | undefined;
19
20
  knownIssues: string | undefined;
20
21
  resultsDir: string[] | undefined;
@@ -48,6 +48,9 @@ export class RunCommand extends Command {
48
48
  this.historyLimit = Option.String("--history-limit", {
49
49
  description: "Limits the number of history entries to keep (default: unlimited)",
50
50
  });
51
+ this.historyBaseUrl = Option.String("--history-base-url", {
52
+ description: "The public base URL of the generated report directory",
53
+ });
51
54
  this.hideLabels = Option.Array("--hide-labels", {
52
55
  description: "Hide labels by exact name in generated reports. Repeat the option for multiple labels",
53
56
  });
@@ -101,6 +104,7 @@ export class RunCommand extends Command {
101
104
  port: this.port,
102
105
  hideLabels,
103
106
  historyLimit: this.historyLimit !== undefined ? parseInt(this.historyLimit, 10) : undefined,
107
+ ...(this.historyBaseUrl !== undefined ? { historyBaseUrl: this.historyBaseUrl } : {}),
104
108
  resolutions: { knownIssuesPath: this.knownIssues },
105
109
  });
106
110
  const resultsPatterns = resolveResultsPatterns(this.resultsDir ?? [], config.resultsDir);
@@ -122,7 +126,7 @@ export class RunCommand extends Command {
122
126
  ...config,
123
127
  environment: resolvedEnvironment?.id,
124
128
  qualityGate: withQualityGate ? config.qualityGate : undefined,
125
- dump: this.dump,
129
+ dump: this.dump ?? config.dump,
126
130
  realTime: false,
127
131
  plugins: [
128
132
  ...(config.plugins?.length
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import process, { argv } from "node:process";
3
3
  import { Builtins, Cli } from "clipanion";
4
- import { AgentCommand, AGENT_TASK_MAP_HELP, AgentCapabilitiesCommand, AgentInspectCommand, AgentLatestCommand, AgentQueryCommand, AgentSelectCommand, AgentStateDirCommand, Allure2Command, AwesomeCommand, CheckCommand, ClassicCommand, CsvCommand, DashboardCommand, GenerateCommand, HistoryCommand, JiraClearCommand, LogCommand, OpenCommand, QualityGateCommand, ResultsPackCommand, ResultsUnpackCommand, RunCommand, SlackCommand, TestOpsPlanCommand, TestPlanCommand, WatchCommand, isAgentTaskMapHelpRequest, } from "./commands/index.js";
4
+ import { AgentCommand, AGENT_TASK_MAP_HELP, AgentCapabilitiesCommand, AgentInspectCommand, AgentLatestCommand, AgentQueryCommand, AgentSelectCommand, AgentStateDirCommand, Allure2Command, AwesomeCommand, CheckCommand, ClassicCommand, CsvCommand, DashboardCommand, GenerateCommand, GitlabGenerateCommand, HistoryCommand, JiraClearCommand, LogCommand, OpenCommand, QualityGateCommand, ResultsPackCommand, ResultsUnpackCommand, RunCommand, SlackCommand, TestOpsPlanCommand, TestPlanCommand, WatchCommand, isAgentTaskMapHelpRequest, } 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({
@@ -35,6 +35,7 @@ cli.register(TestPlanCommand);
35
35
  cli.register(WatchCommand);
36
36
  cli.register(ResultsPackCommand);
37
37
  cli.register(ResultsUnpackCommand);
38
+ cli.register(GitlabGenerateCommand);
38
39
  cli.register(Builtins.HelpCommand);
39
40
  cli.register(Builtins.VersionCommand);
40
41
  void cli
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allure",
3
- "version": "3.17.0",
3
+ "version": "3.19.0",
4
4
  "description": "Allure Commandline Tool",
5
5
  "keywords": [
6
6
  "allure",
@@ -32,33 +32,33 @@
32
32
  "lint:fix": "oxlint --import-plugin --fix src test features stories"
33
33
  },
34
34
  "dependencies": {
35
- "@allurereport/charts-api": "3.17.0",
36
- "@allurereport/ci": "3.17.0",
37
- "@allurereport/core": "3.17.0",
38
- "@allurereport/core-api": "3.17.0",
39
- "@allurereport/directory-watcher": "3.17.0",
40
- "@allurereport/plugin-agent": "3.17.0",
41
- "@allurereport/plugin-allure2": "3.17.0",
42
- "@allurereport/plugin-api": "3.17.0",
43
- "@allurereport/plugin-awesome": "3.17.0",
44
- "@allurereport/plugin-classic": "3.17.0",
45
- "@allurereport/plugin-csv": "3.17.0",
46
- "@allurereport/plugin-dashboard": "3.17.0",
47
- "@allurereport/plugin-jira": "3.17.0",
48
- "@allurereport/plugin-log": "3.17.0",
49
- "@allurereport/plugin-progress": "3.17.0",
50
- "@allurereport/plugin-server-reload": "3.17.0",
51
- "@allurereport/plugin-slack": "3.17.0",
52
- "@allurereport/reader-api": "3.17.0",
53
- "@allurereport/service": "3.17.0",
54
- "@allurereport/static-server": "3.17.0",
35
+ "@allurereport/charts-api": "3.19.0",
36
+ "@allurereport/ci": "3.19.0",
37
+ "@allurereport/core": "3.19.0",
38
+ "@allurereport/core-api": "3.19.0",
39
+ "@allurereport/directory-watcher": "3.19.0",
40
+ "@allurereport/plugin-agent": "3.19.0",
41
+ "@allurereport/plugin-allure2": "3.19.0",
42
+ "@allurereport/plugin-api": "3.19.0",
43
+ "@allurereport/plugin-awesome": "3.19.0",
44
+ "@allurereport/plugin-classic": "3.19.0",
45
+ "@allurereport/plugin-csv": "3.19.0",
46
+ "@allurereport/plugin-dashboard": "3.19.0",
47
+ "@allurereport/plugin-jira": "3.19.0",
48
+ "@allurereport/plugin-log": "3.19.0",
49
+ "@allurereport/plugin-progress": "3.19.0",
50
+ "@allurereport/plugin-server-reload": "3.19.0",
51
+ "@allurereport/plugin-slack": "3.19.0",
52
+ "@allurereport/reader-api": "3.19.0",
53
+ "@allurereport/service": "3.19.0",
54
+ "@allurereport/static-server": "3.19.0",
55
55
  "adm-zip": "^0.6.0",
56
56
  "clipanion": "^4.0.0-rc.4",
57
57
  "glob": "^13.0.6",
58
58
  "lodash.omit": "^4.18.0",
59
59
  "prompts": "^2.4.2",
60
60
  "typanion": "^3.14.0",
61
- "yoctocolors": "^2.1.2"
61
+ "yoctocolors": "^2.2.0"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@types/adm-zip": "^0",