allure 3.15.0 → 3.16.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 +16 -7
- package/dist/commands/agent-run.d.ts +1 -0
- package/dist/commands/agent-run.js +11 -6
- package/dist/commands/agent.d.ts +1 -0
- package/dist/commands/agent.js +19 -8
- package/dist/commands/allure2.js +9 -9
- package/dist/commands/awesome.js +9 -9
- package/dist/commands/check.js +1 -1
- package/dist/commands/classic.js +9 -9
- package/dist/commands/commons/generate.d.ts +1 -1
- package/dist/commands/commons/generate.js +7 -3
- package/dist/commands/commons/resultsDiscovery.d.ts +5 -0
- package/dist/commands/commons/resultsDiscovery.js +17 -0
- package/dist/commands/commons/run.d.ts +2 -0
- package/dist/commands/commons/run.js +37 -22
- package/dist/commands/csv.js +9 -9
- package/dist/commands/dashboard.js +9 -9
- package/dist/commands/generate.js +2 -2
- package/dist/commands/history.js +6 -4
- package/dist/commands/jira/clear.js +1 -1
- package/dist/commands/log.js +9 -9
- package/dist/commands/open.js +9 -5
- package/dist/commands/qualityGate.js +4 -4
- package/dist/commands/results/pack.d.ts +1 -0
- package/dist/commands/results/pack.js +12 -14
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.js +19 -7
- package/dist/commands/slack.js +9 -9
- package/dist/commands/testplan.js +7 -5
- package/dist/commands/watch.d.ts +1 -0
- package/dist/commands/watch.js +71 -25
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/process.js +1 -0
- package/dist/utils/resultsPatterns.d.ts +12 -0
- package/dist/utils/resultsPatterns.js +37 -0
- package/dist/utils/signals.d.ts +18 -0
- package/dist/utils/signals.js +52 -0
- package/package.json +21 -21
package/dist/commands/history.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
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);
|
|
@@ -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)",
|
package/dist/commands/log.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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",
|
package/dist/commands/open.js
CHANGED
|
@@ -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
|
|
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 (
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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",
|
|
@@ -81,7 +81,7 @@ export class QualityGateCommand extends Command {
|
|
|
81
81
|
exit(-1);
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
84
|
-
const { resultDirectories, patterns } = await
|
|
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);
|
|
@@ -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
|
|
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
|
|
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 (
|
|
45
|
-
console.error(red(`No test results directories found matching pattern: ${
|
|
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
|
|
47
|
+
for (const dir of resultDirectories) {
|
|
50
48
|
const files = await fs.readdir(dir);
|
|
51
49
|
if (files.length === 0) {
|
|
52
50
|
continue;
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -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>;
|
package/dist/commands/run.js
CHANGED
|
@@ -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
|
|
65
|
-
if (!
|
|
66
|
-
throw
|
|
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(" ")}`);
|
|
@@ -100,6 +103,7 @@ export class RunCommand extends Command {
|
|
|
100
103
|
historyLimit: this.historyLimit !== undefined ? parseInt(this.historyLimit, 10) : undefined,
|
|
101
104
|
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;
|
|
@@ -149,6 +153,7 @@ export class RunCommand extends Command {
|
|
|
149
153
|
silent: this.silent,
|
|
150
154
|
ignoreLogs: this.ignoreLogs,
|
|
151
155
|
maxRerun,
|
|
156
|
+
resultsPatterns,
|
|
152
157
|
});
|
|
153
158
|
if (config.open) {
|
|
154
159
|
await serve({
|
|
@@ -165,7 +170,10 @@ export class RunCommand extends Command {
|
|
|
165
170
|
RunCommand.paths = [["run"]];
|
|
166
171
|
RunCommand.usage = Command.Usage({
|
|
167
172
|
description: "Run specified command",
|
|
168
|
-
details: "This command runs the specified command and collects Allure results."
|
|
173
|
+
details: "This command runs the specified command and collects Allure results. " +
|
|
174
|
+
"Override results discovery with repeated `--results-dir` (CLI overrides `config.resultsDir`). " +
|
|
175
|
+
"When neither is set, directories named `allure-results` are discovered dynamically. " +
|
|
176
|
+
"Quote globs in the shell so they are not expanded early.",
|
|
169
177
|
examples: [
|
|
170
178
|
["run -- npm run test", "Run npm run test and collect Allure results"],
|
|
171
179
|
["run --rerun 3 -- npm run test", "Run npm run test and rerun failed tests up to 3 times"],
|
|
@@ -173,5 +181,9 @@ RunCommand.usage = Command.Usage({
|
|
|
173
181
|
"run --dump=my-dump -- npm run test",
|
|
174
182
|
"Run npm run test and pack inner report state into my-dump.zip archive to restore the state in the next run",
|
|
175
183
|
],
|
|
184
|
+
[
|
|
185
|
+
"run --results-dir './artifacts/**/allure-results' -- npm test",
|
|
186
|
+
"Override results discovery with a quoted glob",
|
|
187
|
+
],
|
|
176
188
|
],
|
|
177
189
|
});
|
package/dist/commands/slack.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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",
|
|
@@ -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 {
|
|
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
|
|
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
|
|
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);
|
package/dist/commands/watch.d.ts
CHANGED
package/dist/commands/watch.js
CHANGED
|
@@ -2,25 +2,26 @@ import * as console from "node:console";
|
|
|
2
2
|
import { realpath } from "node:fs/promises";
|
|
3
3
|
import { rm } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import process
|
|
5
|
+
import process from "node:process";
|
|
6
6
|
import { AllureReport, isFileNotFoundError, readConfig } from "@allurereport/core";
|
|
7
|
-
import { newFilesInDirectoryWatcher } from "@allurereport/directory-watcher";
|
|
7
|
+
import { allureResultsDirectoriesWatcher, newFilesInDirectoryWatcher } from "@allurereport/directory-watcher";
|
|
8
8
|
import Awesome from "@allurereport/plugin-awesome";
|
|
9
9
|
import ProgressPlugin from "@allurereport/plugin-progress";
|
|
10
10
|
import ServerReloadPlugin from "@allurereport/plugin-server-reload";
|
|
11
11
|
import { PathResultFile } from "@allurereport/reader-api";
|
|
12
12
|
import { serve } from "@allurereport/static-server";
|
|
13
13
|
import { Command, Option } from "clipanion";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { resolveResultsPatterns } from "../utils/resultsPatterns.js";
|
|
15
|
+
import { boundedTerminationSignal, notifySignals, waitForAbort } from "../utils/signals.js";
|
|
16
|
+
import { allureResultsDirectoriesGlobWatcher } from "./commons/resultsDiscovery.js";
|
|
16
17
|
export class WatchCommand extends Command {
|
|
17
18
|
constructor() {
|
|
18
19
|
super(...arguments);
|
|
19
20
|
this.resultsDir = Option.Rest({
|
|
20
|
-
name: "Patterns to match test results directories
|
|
21
|
+
name: "Patterns to match test results directories. Overrides config.resultsDir. Empty patterns use name-based discovery.",
|
|
21
22
|
});
|
|
22
23
|
this.config = Option.String("--config,-c", {
|
|
23
|
-
description: "The path Allure config file",
|
|
24
|
+
description: "The path to Allure config file",
|
|
24
25
|
});
|
|
25
26
|
this.cwd = Option.String("--cwd", {
|
|
26
27
|
description: "The working directory for the command to run (default: current working directory)",
|
|
@@ -40,15 +41,12 @@ export class WatchCommand extends Command {
|
|
|
40
41
|
this.preserve = Option.Boolean("--preserve", {
|
|
41
42
|
description: "Don't clear terminal output on the data refresh",
|
|
42
43
|
});
|
|
44
|
+
this.newOnly = Option.Boolean("--new-only", true, {
|
|
45
|
+
description: "Skip whatever test results already exist on disk at startup and only react to results written after the watch has started, instead of ingesting the existing backlog first (default: true). Pass --no-new-only to ingest the existing backlog too",
|
|
46
|
+
});
|
|
43
47
|
}
|
|
44
48
|
async execute() {
|
|
45
49
|
const cwd = await realpath(this.cwd ?? process.cwd());
|
|
46
|
-
const { resultDirectories, patterns } = await findAllureResultDirectories(cwd, this.resultsDir);
|
|
47
|
-
if (!resultDirectories.length) {
|
|
48
|
-
console.error(red(`No test results directories found matching pattern: ${patterns}`));
|
|
49
|
-
exit(1);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
50
|
const before = new Date().getTime();
|
|
53
51
|
process.on("exit", (code) => {
|
|
54
52
|
const after = new Date().getTime();
|
|
@@ -60,6 +58,8 @@ export class WatchCommand extends Command {
|
|
|
60
58
|
open: this.open,
|
|
61
59
|
port: this.port,
|
|
62
60
|
});
|
|
61
|
+
const resultsPatterns = resolveResultsPatterns(this.resultsDir ?? [], config.resultsDir);
|
|
62
|
+
const useDynamicNameDiscovery = resultsPatterns.length === 0;
|
|
63
63
|
try {
|
|
64
64
|
await rm(config.output, { recursive: true });
|
|
65
65
|
}
|
|
@@ -110,39 +110,85 @@ export class WatchCommand extends Command {
|
|
|
110
110
|
});
|
|
111
111
|
await allureReport.start();
|
|
112
112
|
const abortFunctions = [];
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
113
|
+
const perDirectoryWatchers = new Map();
|
|
114
|
+
let isInitialDiscovery = true;
|
|
115
|
+
const onDiscoveryUpdate = async (newDirectories, deletedDirectories) => {
|
|
116
|
+
for (const deletedDir of deletedDirectories) {
|
|
117
|
+
const watcher = perDirectoryWatchers.get(deletedDir);
|
|
118
|
+
if (watcher) {
|
|
119
|
+
await watcher.abort();
|
|
120
|
+
}
|
|
121
|
+
perDirectoryWatchers.delete(deletedDir);
|
|
122
|
+
}
|
|
123
|
+
for (const newDir of newDirectories) {
|
|
124
|
+
if (perDirectoryWatchers.has(newDir)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const watcher = newFilesInDirectoryWatcher(newDir, async (path) => {
|
|
128
|
+
await allureReport.readResult(new PathResultFile(path));
|
|
129
|
+
}, { ignoreInitial: this.newOnly && isInitialDiscovery });
|
|
130
|
+
perDirectoryWatchers.set(newDir, watcher);
|
|
131
|
+
await watcher.initialScan();
|
|
132
|
+
}
|
|
133
|
+
isInitialDiscovery = false;
|
|
134
|
+
};
|
|
135
|
+
const discoveryWatcher = useDynamicNameDiscovery
|
|
136
|
+
? allureResultsDirectoriesWatcher(cwd, onDiscoveryUpdate)
|
|
137
|
+
: allureResultsDirectoriesGlobWatcher(cwd, resultsPatterns, onDiscoveryUpdate, { indexDelay: 600 });
|
|
138
|
+
await discoveryWatcher.initialScan();
|
|
139
|
+
abortFunctions.push(discoveryWatcher.abort);
|
|
140
|
+
abortFunctions.push(async (immediately) => {
|
|
141
|
+
for (const watcher of perDirectoryWatchers.values()) {
|
|
142
|
+
await watcher.abort(immediately);
|
|
143
|
+
}
|
|
144
|
+
perDirectoryWatchers.clear();
|
|
145
|
+
});
|
|
119
146
|
const pluginIdToOpen = config.plugins?.find((plugin) => !!plugin.options.open)?.id;
|
|
120
147
|
if (pluginIdToOpen) {
|
|
121
148
|
await server.open(join(server.url, pluginIdToOpen));
|
|
122
149
|
}
|
|
123
150
|
console.info("Press Ctrl+C to exit");
|
|
124
|
-
|
|
125
|
-
console.log(
|
|
151
|
+
const notifier = notifySignals(["SIGINT", "SIGTERM"], (signal) => {
|
|
152
|
+
console.log(`\nreceived another ${signal}, force exiting...`);
|
|
153
|
+
process.exit(130);
|
|
154
|
+
});
|
|
155
|
+
await waitForAbort(notifier.signal);
|
|
156
|
+
const signalInfo = notifier.info();
|
|
157
|
+
console.log(`\nreceived ${signalInfo?.signal}, stopping (press again to force exit)...`);
|
|
158
|
+
const terminationSignal = boundedTerminationSignal(signalInfo, 5000);
|
|
159
|
+
const cleanup = (async () => {
|
|
126
160
|
for (const abort of abortFunctions) {
|
|
127
|
-
await abort();
|
|
161
|
+
await abort(true);
|
|
128
162
|
}
|
|
129
163
|
await server.stop();
|
|
130
164
|
await allureReport.done();
|
|
131
|
-
|
|
132
|
-
|
|
165
|
+
})();
|
|
166
|
+
const timedOut = await Promise.race([cleanup.then(() => false), waitForAbort(terminationSignal).then(() => true)]);
|
|
167
|
+
if (timedOut) {
|
|
168
|
+
console.log("shutdown is taking too long, force exiting...");
|
|
169
|
+
process.exit(130);
|
|
170
|
+
}
|
|
171
|
+
notifier.dispose();
|
|
172
|
+
process.exit(signalInfo?.code ?? 0);
|
|
133
173
|
}
|
|
134
174
|
}
|
|
135
175
|
WatchCommand.paths = [["watch"]];
|
|
136
176
|
WatchCommand.usage = Command.Usage({
|
|
137
177
|
description: "Watches Allure Results changes in Real-time",
|
|
138
|
-
details: "This command watches for changes in
|
|
178
|
+
details: "This command watches for changes in Allure Results directories and updates the report in real-time. " +
|
|
179
|
+
"CLI patterns and config.resultsDir use live re-glob discovery. " +
|
|
180
|
+
"When both are empty, directories named `allure-results` are discovered dynamically (unlike generate, which defaults to `./**/allure-results`). " +
|
|
181
|
+
"Quote globs in the shell so they are not expanded early.",
|
|
139
182
|
examples: [
|
|
140
183
|
["watch ./allure-results", "Watch for changes in the ./allure-results directory"],
|
|
141
184
|
[
|
|
142
185
|
"watch ./allure-results --port 8080",
|
|
143
186
|
"Watch for changes in the ./allure-results directory and serve the report on port 8080",
|
|
144
187
|
],
|
|
145
|
-
[
|
|
188
|
+
[
|
|
189
|
+
"watch './packages/*/allure-results'",
|
|
190
|
+
"Watch for changes in all Allure result directories matching the pattern",
|
|
191
|
+
],
|
|
146
192
|
[
|
|
147
193
|
"watch ./packages/foo/allure-results ./packages/bar/allure-results",
|
|
148
194
|
"Watch for changes in two Allure result directories",
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
package/dist/utils/process.js
CHANGED
|
@@ -74,6 +74,7 @@ export const runProcess = (params) => {
|
|
|
74
74
|
};
|
|
75
75
|
export const terminationOf = (testProcess) => new Promise((resolve) => {
|
|
76
76
|
testProcess.on("exit", (code) => resolve(code));
|
|
77
|
+
testProcess.on("error", () => resolve(null));
|
|
77
78
|
});
|
|
78
79
|
export const stopProcessTree = async (pid, { signal = "SIGTERM" } = {}) => {
|
|
79
80
|
const tree = new Map();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const normalizeResultsDir: (value?: string | readonly string[]) => string[];
|
|
2
|
+
export declare const resolveResultsPatterns: (cliPatterns?: string | readonly string[] | null, configResultsDir?: string | readonly string[]) => string[];
|
|
3
|
+
export declare const normalizeResultsDirectoryPath: (path: string) => string;
|
|
4
|
+
export type ParsedRunCommand = {
|
|
5
|
+
command: string | undefined;
|
|
6
|
+
commandArgs: string[];
|
|
7
|
+
};
|
|
8
|
+
export declare const parseRunCommand: (args: string[]) => ParsedRunCommand;
|
|
9
|
+
export declare const resolveAndFindResultsDirs: (cwd: string, cliPatterns: readonly string[], configResultsDir?: string | string[]) => Promise<{
|
|
10
|
+
patterns: readonly string[];
|
|
11
|
+
resultDirectories: string[];
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { findAllureResultDirectories } from "./fileSystem.js";
|
|
3
|
+
export const normalizeResultsDir = (value) => {
|
|
4
|
+
if (value === undefined) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
const values = typeof value === "string" ? [value] : [...value];
|
|
8
|
+
return values.filter((entry) => entry.length > 0);
|
|
9
|
+
};
|
|
10
|
+
export const resolveResultsPatterns = (cliPatterns, configResultsDir) => {
|
|
11
|
+
const fromCli = normalizeResultsDir(cliPatterns ?? undefined);
|
|
12
|
+
if (fromCli.length) {
|
|
13
|
+
return fromCli;
|
|
14
|
+
}
|
|
15
|
+
return normalizeResultsDir(configResultsDir);
|
|
16
|
+
};
|
|
17
|
+
export const normalizeResultsDirectoryPath = (path) => {
|
|
18
|
+
return resolve(path.replace(/[/\\]+$/, ""));
|
|
19
|
+
};
|
|
20
|
+
export const parseRunCommand = (args) => {
|
|
21
|
+
let start = 0;
|
|
22
|
+
while (start < args.length && args[start] === "--") {
|
|
23
|
+
start += 1;
|
|
24
|
+
}
|
|
25
|
+
const commandParts = args.slice(start);
|
|
26
|
+
if (!commandParts.length) {
|
|
27
|
+
return { command: undefined, commandArgs: [] };
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
command: commandParts[0],
|
|
31
|
+
commandArgs: commandParts.slice(1),
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
export const resolveAndFindResultsDirs = async (cwd, cliPatterns, configResultsDir) => {
|
|
35
|
+
const patterns = resolveResultsPatterns(cliPatterns, configResultsDir);
|
|
36
|
+
return findAllureResultDirectories(cwd, patterns);
|
|
37
|
+
};
|