allure 3.15.0 → 3.16.1

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.
Files changed (44) hide show
  1. package/README.md +16 -7
  2. package/dist/commands/agent-run.d.ts +1 -0
  3. package/dist/commands/agent-run.js +11 -8
  4. package/dist/commands/agent.d.ts +1 -0
  5. package/dist/commands/agent.js +19 -8
  6. package/dist/commands/allure2.js +10 -10
  7. package/dist/commands/awesome.js +10 -10
  8. package/dist/commands/check.js +1 -1
  9. package/dist/commands/classic.js +10 -10
  10. package/dist/commands/commons/generate.d.ts +1 -1
  11. package/dist/commands/commons/generate.js +7 -3
  12. package/dist/commands/commons/resultsDiscovery.d.ts +5 -0
  13. package/dist/commands/commons/resultsDiscovery.js +17 -0
  14. package/dist/commands/commons/run.d.ts +2 -3
  15. package/dist/commands/commons/run.js +37 -26
  16. package/dist/commands/csv.js +10 -10
  17. package/dist/commands/dashboard.js +9 -9
  18. package/dist/commands/generate.js +5 -4
  19. package/dist/commands/history.js +6 -4
  20. package/dist/commands/index.d.ts +1 -0
  21. package/dist/commands/index.js +1 -0
  22. package/dist/commands/jira/clear.js +1 -1
  23. package/dist/commands/log.js +9 -9
  24. package/dist/commands/open.js +9 -5
  25. package/dist/commands/qualityGate.js +5 -8
  26. package/dist/commands/results/pack.d.ts +1 -0
  27. package/dist/commands/results/pack.js +12 -14
  28. package/dist/commands/run.d.ts +1 -0
  29. package/dist/commands/run.js +20 -10
  30. package/dist/commands/slack.js +9 -9
  31. package/dist/commands/testopsPlan.d.ts +7 -0
  32. package/dist/commands/testopsPlan.js +65 -0
  33. package/dist/commands/testplan.js +7 -5
  34. package/dist/commands/watch.d.ts +1 -0
  35. package/dist/commands/watch.js +71 -25
  36. package/dist/index.js +2 -1
  37. package/dist/utils/index.d.ts +2 -0
  38. package/dist/utils/index.js +2 -0
  39. package/dist/utils/process.js +1 -0
  40. package/dist/utils/resultsPatterns.d.ts +12 -0
  41. package/dist/utils/resultsPatterns.js +37 -0
  42. package/dist/utils/signals.d.ts +18 -0
  43. package/dist/utils/signals.js +52 -0
  44. package/package.json +21 -21
@@ -5,15 +5,15 @@ import { AllureReport, readConfig } from "@allurereport/core";
5
5
  import DashboardPlugin from "@allurereport/plugin-dashboard";
6
6
  import { Command, Option } from "clipanion";
7
7
  import { red } from "yoctocolors";
8
- import { findAllureResultDirectories } from "../utils/fileSystem.js";
8
+ import { resolveAndFindResultsDirs } from "../utils/resultsPatterns.js";
9
9
  export class DashboardCommand extends Command {
10
10
  constructor() {
11
11
  super(...arguments);
12
12
  this.resultsDir = Option.Rest({
13
- name: "Patterns to match test results directories in the current working directory (default: ./**/allure-results)",
13
+ name: "Patterns to match test results directories. Overrides config.resultsDir. Defaults to ./**/allure-results when neither is set.",
14
14
  });
15
15
  this.config = Option.String("--config,-c", {
16
- description: "The path Allure config file",
16
+ description: "The path to Allure config file",
17
17
  });
18
18
  this.cwd = Option.String("--cwd", {
19
19
  description: "The working directory for the command to run (default: current working directory)",
@@ -39,12 +39,6 @@ export class DashboardCommand extends Command {
39
39
  }
40
40
  async execute() {
41
41
  const cwd = await realpath(this.cwd ?? process.cwd());
42
- const { resultDirectories, patterns } = await findAllureResultDirectories(cwd, this.resultsDir);
43
- if (!resultDirectories.length) {
44
- console.error(red(`No test results directories found matching pattern: ${patterns}`));
45
- exit(1);
46
- return;
47
- }
48
42
  const before = new Date().getTime();
49
43
  const defaultDashboardOptions = {
50
44
  singleFile: this.singleFile ?? false,
@@ -56,6 +50,12 @@ export class DashboardCommand extends Command {
56
50
  output: this.output,
57
51
  name: this.reportName,
58
52
  });
53
+ const { resultDirectories, patterns } = await resolveAndFindResultsDirs(cwd, this.resultsDir, config.resultsDir);
54
+ if (!resultDirectories.length) {
55
+ console.error(red(`No test results directories found matching pattern: ${patterns}`));
56
+ exit(1);
57
+ return;
58
+ }
59
59
  config.plugins = [
60
60
  {
61
61
  id: "dashboard",
@@ -7,10 +7,10 @@ export class GenerateCommand extends Command {
7
7
  constructor() {
8
8
  super(...arguments);
9
9
  this.resultsDir = Option.Rest({
10
- name: "Patterns to match test results directories in the current working directory (default: ./**/allure-results)",
10
+ name: "Patterns to match test results directories. Overrides config.resultsDir. Defaults to ./**/allure-results when neither is set.",
11
11
  });
12
12
  this.config = Option.String("--config,-c", {
13
- description: "The path Allure config file",
13
+ description: "The path to Allure config file",
14
14
  });
15
15
  this.output = Option.String("--output,-o", {
16
16
  description: "The output directory name. Absolute paths are accepted as well (default: allure-report)",
@@ -39,7 +39,8 @@ export class GenerateCommand extends Command {
39
39
  description: "Hide labels by exact name in generated reports. Repeat the option for multiple labels",
40
40
  });
41
41
  this.knownIssues = Option.String("--known-issues", {
42
- description: "Path to known issues file",
42
+ description: "Path to known issues file. " +
43
+ "Allure loads the file and updates it every time with the actual resolutions data of type `issue`",
43
44
  });
44
45
  }
45
46
  async execute() {
@@ -52,7 +53,7 @@ export class GenerateCommand extends Command {
52
53
  port: this.port,
53
54
  hideLabels,
54
55
  historyLimit: this.historyLimit !== undefined ? parseInt(this.historyLimit, 10) : undefined,
55
- knownIssuesPath: this.knownIssues,
56
+ resolutions: { knownIssuesPath: this.knownIssues },
56
57
  });
57
58
  await generate({
58
59
  dump: this.dump,
@@ -1,14 +1,14 @@
1
1
  import * as console from "node:console";
2
2
  import process, { exit } from "node:process";
3
- import { AllureReport, resolveConfig } from "@allurereport/core";
3
+ import { AllureReport, readRawConfig, resolveConfig } from "@allurereport/core";
4
4
  import { Command, Option } from "clipanion";
5
5
  import { red } from "yoctocolors";
6
- import { findAllureResultDirectories } from "../utils/fileSystem.js";
6
+ import { resolveAndFindResultsDirs } from "../utils/resultsPatterns.js";
7
7
  export class HistoryCommand extends Command {
8
8
  constructor() {
9
9
  super(...arguments);
10
10
  this.resultsDir = Option.Rest({
11
- name: "Patterns to match test results directories in the current working directory (default: ./**/allure-results)",
11
+ name: "Patterns to match test results directories. Overrides config.resultsDir. Defaults to ./**/allure-results when neither is set.",
12
12
  });
13
13
  this.historyPath = Option.String("--history-path,-h", {
14
14
  description: "The path to history file",
@@ -21,7 +21,9 @@ export class HistoryCommand extends Command {
21
21
  });
22
22
  }
23
23
  async execute() {
24
- const { resultDirectories, patterns } = await findAllureResultDirectories(process.cwd(), this.resultsDir);
24
+ const cwd = process.cwd();
25
+ const rawConfig = await readRawConfig(cwd);
26
+ const { resultDirectories, patterns } = await resolveAndFindResultsDirs(cwd, this.resultsDir, rawConfig.resultsDir);
25
27
  if (!resultDirectories.length) {
26
28
  console.error(red(`No test results directories found matching pattern: ${patterns}`));
27
29
  exit(1);
@@ -10,6 +10,7 @@ export * from "./history.js";
10
10
  export * from "./run.js";
11
11
  export * from "./slack.js";
12
12
  export * from "./testplan.js";
13
+ export * from "./testopsPlan.js";
13
14
  export * from "./watch.js";
14
15
  export * from "./open.js";
15
16
  export * from "./qualityGate.js";
@@ -10,6 +10,7 @@ export * from "./history.js";
10
10
  export * from "./run.js";
11
11
  export * from "./slack.js";
12
12
  export * from "./testplan.js";
13
+ export * from "./testopsPlan.js";
13
14
  export * from "./watch.js";
14
15
  export * from "./open.js";
15
16
  export * from "./qualityGate.js";
@@ -8,7 +8,7 @@ class BaseJiraCommand extends Command {
8
8
  constructor() {
9
9
  super(...arguments);
10
10
  this.config = Option.String("--config,-c", {
11
- description: "The path Allure config file",
11
+ description: "The path to Allure config file",
12
12
  });
13
13
  this.cwd = Option.String("--cwd", {
14
14
  description: "The working directory for the command to run (default: current working directory)",
@@ -5,15 +5,15 @@ 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 { findAllureResultDirectories } from "../utils/fileSystem.js";
8
+ import { resolveAndFindResultsDirs } from "../utils/resultsPatterns.js";
9
9
  export class LogCommand extends Command {
10
10
  constructor() {
11
11
  super(...arguments);
12
12
  this.resultsDir = Option.Rest({
13
- name: "Patterns to match test results directories in the current working directory (default: ./**/allure-results)",
13
+ name: "Patterns to match test results directories. Overrides config.resultsDir. Defaults to ./**/allure-results when neither is set.",
14
14
  });
15
15
  this.config = Option.String("--config,-c", {
16
- description: "The path Allure config file",
16
+ description: "The path to Allure config file",
17
17
  });
18
18
  this.cwd = Option.String("--cwd", {
19
19
  description: "The working directory for the command to run (default: current working directory)",
@@ -30,12 +30,6 @@ export class LogCommand extends Command {
30
30
  }
31
31
  async execute() {
32
32
  const cwd = await realpath(this.cwd ?? process.cwd());
33
- const { resultDirectories, patterns } = await findAllureResultDirectories(cwd, this.resultsDir);
34
- if (!resultDirectories.length) {
35
- console.error(red(`No test results directories found matching pattern: ${patterns}`));
36
- exit(1);
37
- return;
38
- }
39
33
  const before = new Date().getTime();
40
34
  const defaultLogOptions = {
41
35
  allSteps: this.allSteps ?? false,
@@ -43,6 +37,12 @@ export class LogCommand extends Command {
43
37
  groupBy: this.groupBy ?? "suite",
44
38
  };
45
39
  const config = await readConfig(cwd, this.config);
40
+ const { resultDirectories, patterns } = await resolveAndFindResultsDirs(cwd, this.resultsDir, config.resultsDir);
41
+ if (!resultDirectories.length) {
42
+ console.error(red(`No test results directories found matching pattern: ${patterns}`));
43
+ exit(1);
44
+ return;
45
+ }
46
46
  config.plugins = [
47
47
  {
48
48
  id: "log",
@@ -8,15 +8,17 @@ import { serve } from "@allurereport/static-server";
8
8
  import { Command, Option } from "clipanion";
9
9
  import { red } from "yoctocolors";
10
10
  import { findFilesByGlobs } from "./../utils/fileSystem.js";
11
+ import { resolveResultsPatterns } from "./../utils/resultsPatterns.js";
12
+ import { notifySignals, waitForAbort } from "./../utils/signals.js";
11
13
  import { generate } from "./commons/generate.js";
12
14
  export class OpenCommand extends Command {
13
15
  constructor() {
14
16
  super(...arguments);
15
17
  this.resultsDir = Option.Rest({
16
- name: "A report to open or a pattern to match test results directories in the current working directory (default: configured output)",
18
+ name: "A report path to open, or results patterns. Overrides config.resultsDir. Empty Rest serves the configured output.",
17
19
  });
18
20
  this.config = Option.String("--config,-c", {
19
- description: "The path Allure config file",
21
+ description: "The path to Allure config file",
20
22
  });
21
23
  this.port = Option.String("--port", {
22
24
  description: "The port to serve the reports on. If not set, the server starts on a random port",
@@ -35,6 +37,7 @@ export class OpenCommand extends Command {
35
37
  port: this.port,
36
38
  });
37
39
  const servePath = this.resolveReportPath(cwd, this.resultsDir, config.output);
40
+ const resolvedPatterns = resolveResultsPatterns(this.resultsDir, config.resultsDir);
38
41
  if (await this.reportExists(servePath)) {
39
42
  await serve({
40
43
  port: config.port ? parseInt(config.port, 10) : undefined,
@@ -42,7 +45,7 @@ export class OpenCommand extends Command {
42
45
  open: true,
43
46
  });
44
47
  }
45
- else if (this.resultsDir.length) {
48
+ else if (resolvedPatterns.length) {
46
49
  const tmpDir = await mkdtemp(join(tmpdir(), "allure-report-"));
47
50
  const config = await readConfig(cwd, this.config, {
48
51
  port: this.port,
@@ -54,12 +57,13 @@ export class OpenCommand extends Command {
54
57
  cwd,
55
58
  config,
56
59
  });
57
- process.on("SIGINT", async () => {
60
+ const notifier = notifySignals(["SIGINT", "SIGTERM"]);
61
+ void waitForAbort(notifier.signal).then(async () => {
58
62
  try {
59
63
  await rm(config.output, { recursive: true });
60
64
  }
61
65
  catch { }
62
- process.exit(0);
66
+ process.exit(notifier.info()?.code ?? 0);
63
67
  });
64
68
  await serve({
65
69
  port: config.port ? parseInt(config.port, 10) : undefined,
@@ -6,15 +6,15 @@ import { Command, Option } from "clipanion";
6
6
  import * as typanion from "typanion";
7
7
  import { red } from "yoctocolors";
8
8
  import { environmentNameOption, environmentOption, normalizeCommandEnvironmentOptions, resolveCommandEnvironment, } from "../utils/environment.js";
9
- import { findAllureResultDirectories } from "../utils/fileSystem.js";
9
+ import { resolveAndFindResultsDirs } from "../utils/resultsPatterns.js";
10
10
  export class QualityGateCommand extends Command {
11
11
  constructor() {
12
12
  super(...arguments);
13
13
  this.resultsDir = Option.Rest({
14
- name: "Patterns to match test results directories in the current working directory (default: ./**/allure-results)",
14
+ name: "Patterns to match test results directories. Overrides config.resultsDir. Defaults to ./**/allure-results when neither is set.",
15
15
  });
16
16
  this.config = Option.String("--config,-c", {
17
- description: "The path Allure config file",
17
+ description: "The path to Allure config file",
18
18
  });
19
19
  this.fastFail = Option.Boolean("--fast-fail", {
20
20
  description: "Force the command to fail if there are any rule failures",
@@ -49,7 +49,7 @@ export class QualityGateCommand extends Command {
49
49
  const cwd = await realpath(this.cwd ?? processCwd());
50
50
  const { maxFailures, minTestsCount, successRate, fastFail } = this;
51
51
  const config = await readConfig(cwd, this.config, {
52
- knownIssuesPath: this.knownIssues,
52
+ resolutions: { knownIssuesPath: this.knownIssues },
53
53
  });
54
54
  const resolvedEnvironment = resolveCommandEnvironment(config, environmentOptions);
55
55
  const rules = {};
@@ -81,13 +81,12 @@ export class QualityGateCommand extends Command {
81
81
  exit(-1);
82
82
  return;
83
83
  }
84
- const { resultDirectories, patterns } = await findAllureResultDirectories(cwd, this.resultsDir);
84
+ const { resultDirectories, patterns } = await resolveAndFindResultsDirs(cwd, this.resultsDir, config.resultsDir);
85
85
  if (!resultDirectories.length) {
86
86
  console.error(red(`No test results directories found matching pattern: ${patterns}`));
87
87
  exit(1);
88
88
  return;
89
89
  }
90
- const knownIssues = await allureReport.store.allKnownIssues();
91
90
  const state = new QualityGateState();
92
91
  allureReport.realtimeSubscriber.onTestResults(async (trsIds) => {
93
92
  const trs = await Promise.all(trsIds.map((id) => allureReport.store.testResultById(id)));
@@ -95,7 +94,6 @@ export class QualityGateCommand extends Command {
95
94
  const { results, fastFailed } = await allureReport.validate({
96
95
  trs: nonRetryTrs,
97
96
  environment: resolvedEnvironment?.id,
98
- knownIssues,
99
97
  state,
100
98
  });
101
99
  if (!fastFailed) {
@@ -112,7 +110,6 @@ export class QualityGateCommand extends Command {
112
110
  const allTrs = await allureReport.store.allTestResults({ includeRetries: false });
113
111
  const validationResults = await allureReport.validate({
114
112
  trs: allTrs,
115
- knownIssues,
116
113
  environment: resolvedEnvironment?.id,
117
114
  });
118
115
  if (validationResults.results.length === 0) {
@@ -5,6 +5,7 @@ export declare class ResultsPackCommand extends Command {
5
5
  static usage: import("clipanion").Usage;
6
6
  resultsDir: string | undefined;
7
7
  name: string | undefined;
8
+ config: string | undefined;
8
9
  cwd: string | undefined;
9
10
  execute(): Promise<void>;
10
11
  }
@@ -9,44 +9,42 @@ import * as fs from "node:fs/promises";
9
9
  import { realpath } from "node:fs/promises";
10
10
  import { basename, join, resolve } from "node:path";
11
11
  import { exit } from "node:process";
12
+ import { readRawConfig } from "@allurereport/core";
12
13
  import AdmZip from "adm-zip";
13
14
  import { Command, Option } from "clipanion";
14
- import { glob } from "glob";
15
15
  import { green, red } from "yoctocolors";
16
+ import { resolveAndFindResultsDirs } from "../../utils/resultsPatterns.js";
16
17
  export class ResultsPackCommand extends Command {
17
18
  constructor() {
18
19
  super(...arguments);
19
20
  _ResultsPackCommand_instances.add(this);
20
21
  this.resultsDir = Option.String({
21
22
  required: false,
22
- name: "Pattern to match test results directories in the current working directory (default: ./**/allure-results)",
23
+ name: "Pattern to match test results directories. Overrides config.resultsDir. Defaults to ./**/allure-results when neither is set.",
23
24
  });
24
25
  this.name = Option.String("--name", {
25
26
  description: "The archive name (default: allure-results.zip)",
26
27
  });
28
+ this.config = Option.String("--config,-c", {
29
+ description: "The path to Allure config file",
30
+ });
27
31
  this.cwd = Option.String("--cwd", {
28
32
  description: "The working directory for the command to run (default: current working directory)",
29
33
  });
30
34
  }
31
35
  async execute() {
32
36
  const cwd = await realpath(this.cwd ?? process.cwd());
33
- const resultsDir = (this.resultsDir ?? "./**/allure-results").replace(/[\\/]$/, "");
37
+ const rawConfig = await readRawConfig(cwd, this.config);
38
+ const cliPatterns = this.resultsDir ? [this.resultsDir] : [];
39
+ const { resultDirectories, patterns } = await resolveAndFindResultsDirs(cwd, cliPatterns, rawConfig.resultsDir);
34
40
  const archiveName = this.name ?? "allure-results.zip";
35
- const resultsDirectories = (await glob(resultsDir, {
36
- mark: true,
37
- nodir: false,
38
- dot: true,
39
- absolute: true,
40
- windowsPathsNoEscape: true,
41
- cwd,
42
- })).filter((p) => /(\/|\\)$/.test(p));
43
41
  const resultsFiles = new Set();
44
- if (resultsDirectories.length === 0) {
45
- console.error(red(`No test results directories found matching pattern: ${resultsDir}`));
42
+ if (resultDirectories.length === 0) {
43
+ console.error(red(`No test results directories found matching pattern: ${patterns}`));
46
44
  exit(1);
47
45
  return;
48
46
  }
49
- for (const dir of resultsDirectories) {
47
+ for (const dir of resultDirectories) {
50
48
  const files = await fs.readdir(dir);
51
49
  if (files.length === 0) {
52
50
  continue;
@@ -17,6 +17,7 @@ export declare class RunCommand extends Command {
17
17
  historyLimit: string | undefined;
18
18
  hideLabels: string[] | undefined;
19
19
  knownIssues: string | undefined;
20
+ resultsDir: string[] | undefined;
20
21
  commandToRun: string[];
21
22
  get logs(): "pipe" | "inherit" | "ignore";
22
23
  execute(): Promise<void>;
@@ -7,12 +7,14 @@ import { serve } from "@allurereport/static-server";
7
7
  import { Command, Option, UsageError } from "clipanion";
8
8
  import { environmentNameOption, environmentOption, normalizeCommandEnvironmentOptions, resolveCommandEnvironment, } from "../utils/environment.js";
9
9
  import { createChildAllureCliEnvironment, getActiveAllureCliCommand } from "../utils/execution-context.js";
10
+ import { parseRunCommand, resolveResultsPatterns } from "../utils/resultsPatterns.js";
10
11
  import { executeAllureRun, executeNestedAllureCommand } from "./commons/run.js";
12
+ const missingRunCommandUsageError = () => new UsageError("expecting command to be specified after --, e.g. allure run -- npm run test");
11
13
  export class RunCommand extends Command {
12
14
  constructor() {
13
15
  super(...arguments);
14
16
  this.config = Option.String("--config,-c", {
15
- description: "The path Allure config file",
17
+ description: "The path to Allure config file",
16
18
  });
17
19
  this.cwd = Option.String("--cwd", {
18
20
  description: "The working directory for the command to run (default: current working directory)",
@@ -52,6 +54,9 @@ export class RunCommand extends Command {
52
54
  this.knownIssues = Option.String("--known-issues", {
53
55
  description: "Path to known issues file",
54
56
  });
57
+ this.resultsDir = Option.Array("--results-dir", {
58
+ description: "Glob pattern or path for Allure results directories (repeatable). Overrides config.resultsDir. Quote globs in the shell",
59
+ });
55
60
  this.commandToRun = Option.Rest();
56
61
  }
57
62
  get logs() {
@@ -61,17 +66,15 @@ export class RunCommand extends Command {
61
66
  return this.ignoreLogs ? "inherit" : "pipe";
62
67
  }
63
68
  async execute() {
64
- const args = this.commandToRun.filter((arg) => arg !== "--");
65
- if (!args || !args.length) {
66
- throw new UsageError("expecting command to be specified after --, e.g. allure run -- npm run test");
69
+ const { command, commandArgs } = parseRunCommand(this.commandToRun);
70
+ if (!command) {
71
+ throw missingRunCommandUsageError();
67
72
  }
68
73
  const before = new Date().getTime();
69
74
  process.on("exit", (exitCode) => {
70
75
  const after = new Date().getTime();
71
76
  console.log(`exit code ${exitCode} (${after - before}ms)`);
72
77
  });
73
- const command = args[0];
74
- const commandArgs = args.slice(1);
75
78
  const cwd = await realpath(this.cwd ?? process.cwd());
76
79
  const hideLabels = this.hideLabels?.length ? this.hideLabels : undefined;
77
80
  console.log(`${command} ${commandArgs.join(" ")}`);
@@ -98,8 +101,9 @@ export class RunCommand extends Command {
98
101
  port: this.port,
99
102
  hideLabels,
100
103
  historyLimit: this.historyLimit !== undefined ? parseInt(this.historyLimit, 10) : undefined,
101
- knownIssuesPath: this.knownIssues,
104
+ resolutions: { knownIssuesPath: this.knownIssues },
102
105
  });
106
+ const resultsPatterns = resolveResultsPatterns(this.resultsDir ?? [], config.resultsDir);
103
107
  const resolvedEnvironment = resolveCommandEnvironment(config, environmentOptions);
104
108
  const withRerun = maxRerun > 0;
105
109
  const withQualityGate = !!config.qualityGate && !withRerun;
@@ -135,10 +139,8 @@ export class RunCommand extends Command {
135
139
  ]),
136
140
  ],
137
141
  });
138
- const knownIssues = await allureReport.store.allKnownIssues();
139
142
  const { globalExitCode } = await executeAllureRun({
140
143
  allureReport,
141
- knownIssues,
142
144
  cwd,
143
145
  command,
144
146
  commandArgs,
@@ -149,6 +151,7 @@ export class RunCommand extends Command {
149
151
  silent: this.silent,
150
152
  ignoreLogs: this.ignoreLogs,
151
153
  maxRerun,
154
+ resultsPatterns,
152
155
  });
153
156
  if (config.open) {
154
157
  await serve({
@@ -165,7 +168,10 @@ export class RunCommand extends Command {
165
168
  RunCommand.paths = [["run"]];
166
169
  RunCommand.usage = Command.Usage({
167
170
  description: "Run specified command",
168
- details: "This command runs the specified command and collects Allure results.",
171
+ details: "This command runs the specified command and collects Allure results. " +
172
+ "Override results discovery with repeated `--results-dir` (CLI overrides `config.resultsDir`). " +
173
+ "When neither is set, directories named `allure-results` are discovered dynamically. " +
174
+ "Quote globs in the shell so they are not expanded early.",
169
175
  examples: [
170
176
  ["run -- npm run test", "Run npm run test and collect Allure results"],
171
177
  ["run --rerun 3 -- npm run test", "Run npm run test and rerun failed tests up to 3 times"],
@@ -173,5 +179,9 @@ RunCommand.usage = Command.Usage({
173
179
  "run --dump=my-dump -- npm run test",
174
180
  "Run npm run test and pack inner report state into my-dump.zip archive to restore the state in the next run",
175
181
  ],
182
+ [
183
+ "run --results-dir './artifacts/**/allure-results' -- npm test",
184
+ "Override results discovery with a quoted glob",
185
+ ],
176
186
  ],
177
187
  });
@@ -5,15 +5,15 @@ import { AllureReport, readConfig } from "@allurereport/core";
5
5
  import SlackPlugin from "@allurereport/plugin-slack";
6
6
  import { Command, Option } from "clipanion";
7
7
  import { red } from "yoctocolors";
8
- import { findAllureResultDirectories } from "../utils/fileSystem.js";
8
+ import { resolveAndFindResultsDirs } from "../utils/resultsPatterns.js";
9
9
  export class SlackCommand extends Command {
10
10
  constructor() {
11
11
  super(...arguments);
12
12
  this.resultsDir = Option.Rest({
13
- name: "Patterns to match test results directories in the current working directory (default: ./**/allure-results)",
13
+ name: "Patterns to match test results directories. Overrides config.resultsDir. Defaults to ./**/allure-results when neither is set.",
14
14
  });
15
15
  this.config = Option.String("--config,-c", {
16
- description: "The path Allure config file",
16
+ description: "The path to Allure config file",
17
17
  });
18
18
  this.cwd = Option.String("--cwd", {
19
19
  description: "The working directory for the command to run (default: current working directory)",
@@ -29,18 +29,18 @@ export class SlackCommand extends Command {
29
29
  }
30
30
  async execute() {
31
31
  const cwd = await realpath(this.cwd ?? process.cwd());
32
- const { resultDirectories, patterns } = await findAllureResultDirectories(cwd, this.resultsDir);
33
- if (!resultDirectories.length) {
34
- console.error(red(`No test results directories found matching pattern: ${patterns}`));
35
- exit(1);
36
- return;
37
- }
38
32
  const before = new Date().getTime();
39
33
  const defaultSlackOptions = {
40
34
  token: this.token,
41
35
  channel: this.channel,
42
36
  };
43
37
  const config = await readConfig(cwd, this.config);
38
+ const { resultDirectories, patterns } = await resolveAndFindResultsDirs(cwd, this.resultsDir, config.resultsDir);
39
+ if (!resultDirectories.length) {
40
+ console.error(red(`No test results directories found matching pattern: ${patterns}`));
41
+ exit(1);
42
+ return;
43
+ }
44
44
  config.plugins = [
45
45
  {
46
46
  id: "slack",
@@ -0,0 +1,7 @@
1
+ import { Command } from "clipanion";
2
+ export declare class TestOpsPlanCommand extends Command {
3
+ static paths: string[][];
4
+ static usage: import("clipanion").Usage;
5
+ output: string | undefined;
6
+ execute(): Promise<void>;
7
+ }
@@ -0,0 +1,65 @@
1
+ import * as console from "node:console";
2
+ import { rm, writeFile } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ import { env, exit } from "node:process";
5
+ import { applyAllureCiEnv } from "@allurereport/ci";
6
+ import { createServiceHttpClient } from "@allurereport/service";
7
+ import { Command, Option } from "clipanion";
8
+ import { red } from "yoctocolors";
9
+ export class TestOpsPlanCommand extends Command {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.output = Option.String("--output,-o", {
13
+ description: "The output file name. Absolute paths are accepted as well (default: ./testplan.json)",
14
+ });
15
+ }
16
+ async execute() {
17
+ applyAllureCiEnv();
18
+ const jobRunId = Number(env.ALLURE_JOB_RUN_ID);
19
+ if (!Number.isInteger(jobRunId) || jobRunId <= 0) {
20
+ console.log("ALLURE_JOB_RUN_ID isn't set, skipping test plan generation");
21
+ return;
22
+ }
23
+ const endpoint = env.ALLURE_ENDPOINT;
24
+ const accessToken = env.ALLURE_TOKEN;
25
+ if (!endpoint || !accessToken) {
26
+ console.error(red("ALLURE_ENDPOINT and ALLURE_TOKEN are required to fetch a test plan from TestOps"));
27
+ exit(1);
28
+ return;
29
+ }
30
+ const client = createServiceHttpClient(endpoint, { apiToken: accessToken });
31
+ const output = resolve(this.output ?? "./testplan.json");
32
+ let tests;
33
+ try {
34
+ tests = await client.get(`/api/rs/jobrun/${jobRunId}/plan`, {
35
+ params: { expected: "true" },
36
+ });
37
+ }
38
+ catch (error) {
39
+ const message = error instanceof Error ? error.message : String(error);
40
+ await rm(output, { force: true });
41
+ console.error(red(`Could not fetch the test plan for job run ${jobRunId}, continuing without one: ${message}`));
42
+ return;
43
+ }
44
+ const testPlan = {
45
+ version: "1.0",
46
+ tests: (tests ?? []).map(({ id, selector }) => ({
47
+ ...(id !== undefined ? { id: String(id) } : {}),
48
+ ...(selector !== undefined ? { selector } : {}),
49
+ })),
50
+ };
51
+ await writeFile(output, JSON.stringify(testPlan), "utf-8");
52
+ console.log(`test plan for job run ${jobRunId} written to ${output} (${testPlan.tests.length} test(s))`);
53
+ }
54
+ }
55
+ TestOpsPlanCommand.paths = [["testops-plan"]];
56
+ TestOpsPlanCommand.usage = Command.Usage({
57
+ description: "Fetches a testplan.json for the current TestOps job run",
58
+ details: "Reads ALLURE_JOB_RUN_ID and, when it's set, downloads the test cases selected for that job run " +
59
+ "from TestOps and writes them as a testplan.json compatible with ALLURE_TESTPLAN_PATH. " +
60
+ "Does nothing when ALLURE_JOB_RUN_ID isn't set, which covers every run TestOps didn't trigger itself.",
61
+ examples: [
62
+ ["testops-plan", "Write ./testplan.json from the current job run, if any"],
63
+ ["testops-plan --output custom-testplan.json", "Write to a custom path instead"],
64
+ ],
65
+ });
@@ -1,22 +1,24 @@
1
1
  import * as console from "node:console";
2
2
  import { basename, dirname, resolve } from "node:path";
3
- import { exit } from "node:process";
4
- import { AllureReport, resolveConfig } from "@allurereport/core";
3
+ import { cwd as processCwd, exit } from "node:process";
4
+ import { AllureReport, readRawConfig, resolveConfig } from "@allurereport/core";
5
5
  import { Command, Option } from "clipanion";
6
6
  import { red } from "yoctocolors";
7
- import { findAllureResultDirectories } from "../utils/fileSystem.js";
7
+ import { resolveAndFindResultsDirs } from "../utils/resultsPatterns.js";
8
8
  export class TestPlanCommand extends Command {
9
9
  constructor() {
10
10
  super(...arguments);
11
11
  this.resultsDir = Option.Rest({
12
- name: "Patterns to match test results directories in the current working directory (default: ./**/allure-results)",
12
+ name: "Patterns to match test results directories. Overrides config.resultsDir. Defaults to ./**/allure-results when neither is set.",
13
13
  });
14
14
  this.output = Option.String("--output,-o", {
15
15
  description: "The output file name. Absolute paths are accepted as well",
16
16
  });
17
17
  }
18
18
  async execute() {
19
- const { resultDirectories, patterns } = await findAllureResultDirectories(process.cwd(), this.resultsDir);
19
+ const cwd = processCwd();
20
+ const rawConfig = await readRawConfig(cwd);
21
+ const { resultDirectories, patterns } = await resolveAndFindResultsDirs(cwd, this.resultsDir, rawConfig.resultsDir);
20
22
  if (!resultDirectories.length) {
21
23
  console.error(red(`No test results directories found matching pattern: ${patterns}`));
22
24
  exit(1);
@@ -10,5 +10,6 @@ export declare class WatchCommand extends Command {
10
10
  open: boolean | undefined;
11
11
  port: string | undefined;
12
12
  preserve: boolean | undefined;
13
+ newOnly: boolean;
13
14
  execute(): Promise<void>;
14
15
  }