allure 3.5.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/commands/agent.d.ts +58 -0
- package/dist/commands/agent.js +374 -0
- package/dist/commands/commons/run.d.ts +48 -0
- package/dist/commands/commons/run.js +258 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.js +1 -0
- package/dist/commands/run.d.ts +0 -7
- package/dist/commands/run.js +51 -238
- package/dist/index.js +5 -1
- package/dist/utils/agent-select.d.ts +41 -0
- package/dist/utils/agent-select.js +141 -0
- package/dist/utils/agent-state.d.ts +15 -0
- package/dist/utils/agent-state.js +83 -0
- package/dist/utils/environment.d.ts +7 -2
- package/dist/utils/environment.js +1 -1
- package/dist/utils/execution-context.d.ts +4 -0
- package/dist/utils/execution-context.js +9 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/process.js +2 -1
- package/package.json +21 -20
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { loadAgentOutput, planAgentEnrichmentReview, } from "@allurereport/plugin-agent";
|
|
5
|
+
import { UsageError } from "clipanion";
|
|
6
|
+
import { readLatestAgentState } from "./agent-state.js";
|
|
7
|
+
const AGENT_RERUN_PRESETS = ["review", "failed", "unsuccessful", "all"];
|
|
8
|
+
const ALLURE_ID_LABEL = "ALLURE_ID";
|
|
9
|
+
const isAgentRerunPreset = (value) => AGENT_RERUN_PRESETS.includes(value);
|
|
10
|
+
const readAllureId = (labels) => labels.find((label) => label.name === ALLURE_ID_LABEL)?.value;
|
|
11
|
+
const matchesLabelFilters = (labels, filters) => filters.every((filter) => labels.some((label) => label.name === filter.name && label.value === filter.value));
|
|
12
|
+
const buildTestPlan = (tests) => {
|
|
13
|
+
const seenSelectors = new Set();
|
|
14
|
+
const seenIds = new Set();
|
|
15
|
+
const selected = [];
|
|
16
|
+
for (const test of tests) {
|
|
17
|
+
const selector = test.full_name || undefined;
|
|
18
|
+
const id = readAllureId(test.labels);
|
|
19
|
+
if (selector && !seenSelectors.has(selector)) {
|
|
20
|
+
seenSelectors.add(selector);
|
|
21
|
+
if (id) {
|
|
22
|
+
seenIds.add(id);
|
|
23
|
+
}
|
|
24
|
+
selected.push({
|
|
25
|
+
selector,
|
|
26
|
+
...(id ? { id } : {}),
|
|
27
|
+
});
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (id && !seenIds.has(id)) {
|
|
31
|
+
seenIds.add(id);
|
|
32
|
+
selected.push({ id });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
version: "1.0",
|
|
37
|
+
tests: selected,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const selectTestsByPreset = (output, preset) => {
|
|
41
|
+
switch (preset) {
|
|
42
|
+
case "review": {
|
|
43
|
+
const review = planAgentEnrichmentReview(output);
|
|
44
|
+
const targeted = new Set(review.rerun.targetedTests);
|
|
45
|
+
return output.tests.filter((test) => targeted.has(test.full_name));
|
|
46
|
+
}
|
|
47
|
+
case "failed":
|
|
48
|
+
return output.tests.filter((test) => test.status === "failed" || test.status === "broken");
|
|
49
|
+
case "unsuccessful":
|
|
50
|
+
return output.tests.filter((test) => test.status !== "passed");
|
|
51
|
+
case "all":
|
|
52
|
+
return output.tests;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
export const normalizeAgentRerunPreset = (value) => {
|
|
56
|
+
if (!value) {
|
|
57
|
+
return "review";
|
|
58
|
+
}
|
|
59
|
+
const normalized = value.trim().toLowerCase();
|
|
60
|
+
if (!isAgentRerunPreset(normalized)) {
|
|
61
|
+
throw new UsageError(`Invalid rerun preset ${JSON.stringify(value)}. Expected one of: ${AGENT_RERUN_PRESETS.join(", ")}`);
|
|
62
|
+
}
|
|
63
|
+
return normalized;
|
|
64
|
+
};
|
|
65
|
+
export const parseAgentLabelFilters = (values) => (values ?? []).map((value) => {
|
|
66
|
+
const separatorIndex = value.indexOf("=");
|
|
67
|
+
if (separatorIndex <= 0 || separatorIndex === value.length - 1) {
|
|
68
|
+
throw new UsageError(`Invalid label filter ${JSON.stringify(value)}. Expected the form name=value, for example feature=checkout`);
|
|
69
|
+
}
|
|
70
|
+
const name = value.slice(0, separatorIndex).trim();
|
|
71
|
+
const filterValue = value.slice(separatorIndex + 1).trim();
|
|
72
|
+
if (!name || !filterValue) {
|
|
73
|
+
throw new UsageError(`Invalid label filter ${JSON.stringify(value)}. Expected the form name=value, for example feature=checkout`);
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
name,
|
|
77
|
+
value: filterValue,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
export const resolveAgentSelectionOutputDir = async (params) => {
|
|
81
|
+
const { cwd, from, latest } = params;
|
|
82
|
+
if (from && latest) {
|
|
83
|
+
throw new UsageError("Use either --from or --latest, not both");
|
|
84
|
+
}
|
|
85
|
+
if (!from && !latest) {
|
|
86
|
+
throw new UsageError("Expected either --from <path> or --latest");
|
|
87
|
+
}
|
|
88
|
+
if (from) {
|
|
89
|
+
return resolve(cwd, from);
|
|
90
|
+
}
|
|
91
|
+
const latestState = await readLatestAgentState(cwd);
|
|
92
|
+
if (!latestState) {
|
|
93
|
+
throw new UsageError(`No latest agent output found for ${cwd}`);
|
|
94
|
+
}
|
|
95
|
+
return latestState.outputDir;
|
|
96
|
+
};
|
|
97
|
+
export const selectAgentTestPlan = async (params) => {
|
|
98
|
+
const preset = params.preset ?? "review";
|
|
99
|
+
const output = await loadAgentOutput(params.outputDir);
|
|
100
|
+
const selectedTests = selectTestsByPreset(output, preset)
|
|
101
|
+
.filter((test) => (params.environments?.length ? params.environments.includes(test.environment_id) : true))
|
|
102
|
+
.filter((test) => (params.labelFilters?.length ? matchesLabelFilters(test.labels, params.labelFilters) : true));
|
|
103
|
+
return {
|
|
104
|
+
outputDir: output.outputDir,
|
|
105
|
+
preset,
|
|
106
|
+
selectedTests,
|
|
107
|
+
testPlan: buildTestPlan(selectedTests),
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
export const createAgentTestPlanContext = async (params) => {
|
|
111
|
+
const { from, latest } = params;
|
|
112
|
+
if (!from && !latest) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const outputDir = await resolveAgentSelectionOutputDir({
|
|
116
|
+
cwd: params.cwd,
|
|
117
|
+
from,
|
|
118
|
+
latest,
|
|
119
|
+
});
|
|
120
|
+
const selection = await selectAgentTestPlan({
|
|
121
|
+
outputDir,
|
|
122
|
+
preset: params.preset,
|
|
123
|
+
environments: params.environments,
|
|
124
|
+
labelFilters: params.labelFilters,
|
|
125
|
+
});
|
|
126
|
+
if (!selection.testPlan.tests.length) {
|
|
127
|
+
throw new UsageError(`No tests matched rerun selection in ${selection.outputDir}. Adjust the preset or filters before rerunning.`);
|
|
128
|
+
}
|
|
129
|
+
const tempDir = await mkdtemp(join(tmpdir(), "allure-agent-select-"));
|
|
130
|
+
const testPlanPath = join(tempDir, "testplan.json");
|
|
131
|
+
await writeFile(testPlanPath, `${JSON.stringify(selection.testPlan, null, 2)}\n`, "utf-8");
|
|
132
|
+
return {
|
|
133
|
+
outputDir: selection.outputDir,
|
|
134
|
+
preset: selection.preset,
|
|
135
|
+
selectedCount: selection.testPlan.tests.length,
|
|
136
|
+
testPlanPath,
|
|
137
|
+
cleanup: async () => {
|
|
138
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type AgentLatestState = {
|
|
2
|
+
schema: "allure-agent-latest/v1";
|
|
3
|
+
cwd: string;
|
|
4
|
+
outputDir: string;
|
|
5
|
+
expectationsPath?: string;
|
|
6
|
+
command: string;
|
|
7
|
+
startedAt: string;
|
|
8
|
+
finishedAt?: string;
|
|
9
|
+
status: "running" | "finished";
|
|
10
|
+
exitCode?: number | null;
|
|
11
|
+
};
|
|
12
|
+
export declare const ALLURE_AGENT_STATE_DIR_ENV = "ALLURE_AGENT_STATE_DIR";
|
|
13
|
+
export declare const resolveAgentStateDir: (cwd: string) => string;
|
|
14
|
+
export declare const writeLatestAgentState: (value: Omit<AgentLatestState, "schema">) => Promise<AgentLatestState>;
|
|
15
|
+
export declare const readLatestAgentState: (cwd: string) => Promise<AgentLatestState | undefined>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
const AGENT_STATE_SCHEMA = "allure-agent-latest/v1";
|
|
6
|
+
export const ALLURE_AGENT_STATE_DIR_ENV = "ALLURE_AGENT_STATE_DIR";
|
|
7
|
+
const isFileNotFoundError = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
8
|
+
const projectHash = (cwd) => createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
9
|
+
export const resolveAgentStateDir = (cwd) => {
|
|
10
|
+
const configuredDir = process.env[ALLURE_AGENT_STATE_DIR_ENV]?.trim();
|
|
11
|
+
if (configuredDir) {
|
|
12
|
+
return resolve(configuredDir);
|
|
13
|
+
}
|
|
14
|
+
return join(tmpdir(), `allure-agent-state-${projectHash(resolve(cwd))}`);
|
|
15
|
+
};
|
|
16
|
+
const projectStatePath = (cwd) => join(resolveAgentStateDir(cwd), "latest.json");
|
|
17
|
+
const writeJsonAtomic = async (filePath, value) => {
|
|
18
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
19
|
+
const tempPath = `${filePath}.${process.pid}.tmp`;
|
|
20
|
+
await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf-8");
|
|
21
|
+
await rename(tempPath, filePath);
|
|
22
|
+
};
|
|
23
|
+
const isAgentLatestState = (value) => {
|
|
24
|
+
if (typeof value !== "object" || value === null) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const candidate = value;
|
|
28
|
+
return (candidate.schema === AGENT_STATE_SCHEMA &&
|
|
29
|
+
typeof candidate.cwd === "string" &&
|
|
30
|
+
typeof candidate.outputDir === "string" &&
|
|
31
|
+
typeof candidate.command === "string" &&
|
|
32
|
+
typeof candidate.startedAt === "string" &&
|
|
33
|
+
(candidate.expectationsPath === undefined || typeof candidate.expectationsPath === "string") &&
|
|
34
|
+
(candidate.finishedAt === undefined || typeof candidate.finishedAt === "string") &&
|
|
35
|
+
(candidate.status === "running" || candidate.status === "finished") &&
|
|
36
|
+
(candidate.exitCode === undefined || typeof candidate.exitCode === "number" || candidate.exitCode === null));
|
|
37
|
+
};
|
|
38
|
+
export const writeLatestAgentState = async (value) => {
|
|
39
|
+
const normalizedState = {
|
|
40
|
+
schema: AGENT_STATE_SCHEMA,
|
|
41
|
+
cwd: resolve(value.cwd),
|
|
42
|
+
outputDir: resolve(value.outputDir),
|
|
43
|
+
expectationsPath: value.expectationsPath ? resolve(value.expectationsPath) : undefined,
|
|
44
|
+
command: value.command,
|
|
45
|
+
startedAt: value.startedAt,
|
|
46
|
+
finishedAt: value.finishedAt,
|
|
47
|
+
status: value.status,
|
|
48
|
+
exitCode: value.exitCode,
|
|
49
|
+
};
|
|
50
|
+
await writeJsonAtomic(projectStatePath(normalizedState.cwd), normalizedState);
|
|
51
|
+
return normalizedState;
|
|
52
|
+
};
|
|
53
|
+
export const readLatestAgentState = async (cwd) => {
|
|
54
|
+
const normalizedCwd = resolve(cwd);
|
|
55
|
+
const statePath = projectStatePath(normalizedCwd);
|
|
56
|
+
let raw;
|
|
57
|
+
try {
|
|
58
|
+
raw = await readFile(statePath, "utf-8");
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (isFileNotFoundError(error)) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
const parsed = JSON.parse(raw);
|
|
67
|
+
if (!isAgentLatestState(parsed)) {
|
|
68
|
+
throw new Error(`Invalid latest agent state in ${statePath}`);
|
|
69
|
+
}
|
|
70
|
+
if (parsed.cwd !== normalizedCwd) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await stat(parsed.outputDir);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (isFileNotFoundError(error)) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
return parsed;
|
|
83
|
+
};
|
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
import { type FullConfig } from "@allurereport/core";
|
|
2
1
|
import type { EnvironmentIdentity } from "@allurereport/core-api";
|
|
2
|
+
import { type EnvironmentsConfig } from "@allurereport/core-api";
|
|
3
3
|
type CommandEnvironmentOptions = {
|
|
4
4
|
environment?: string;
|
|
5
5
|
environmentName?: string;
|
|
6
6
|
};
|
|
7
|
+
type EnvironmentConfig = {
|
|
8
|
+
environment?: string;
|
|
9
|
+
environments?: EnvironmentsConfig;
|
|
10
|
+
allowedEnvironments?: string[];
|
|
11
|
+
};
|
|
7
12
|
export declare const environmentOption: () => string | undefined;
|
|
8
13
|
export declare const environmentNameOption: () => string | undefined;
|
|
9
|
-
export declare const resolveCommandEnvironment: (config: Pick<
|
|
14
|
+
export declare const resolveCommandEnvironment: (config: Pick<EnvironmentConfig, "environment" | "environments" | "allowedEnvironments">, options: CommandEnvironmentOptions & {
|
|
10
15
|
source?: string;
|
|
11
16
|
}) => EnvironmentIdentity | undefined;
|
|
12
17
|
export declare const normalizeCommandEnvironmentOptions: (options: CommandEnvironmentOptions) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { environmentIdentityById, environmentIdentityByName, validateAllowedEnvironmentId
|
|
1
|
+
import { environmentIdentityById, environmentIdentityByName, validateAllowedEnvironmentId } from "@allurereport/core";
|
|
2
2
|
import { validateEnvironmentId, validateEnvironmentName } from "@allurereport/core-api";
|
|
3
3
|
import { Option, UsageError } from "clipanion";
|
|
4
4
|
const environmentOptionDescription = "Force specific environment ID to all tests in the run. Given environment has higher priority than the one defined in the config file (default: empty string)";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const ALLURE_CLI_ACTIVE_COMMAND_ENV = "ALLURE_CLI_ACTIVE_COMMAND";
|
|
2
|
+
export type AllureCliActiveCommand = "agent" | "run";
|
|
3
|
+
export declare const getActiveAllureCliCommand: () => AllureCliActiveCommand | undefined;
|
|
4
|
+
export declare const createChildAllureCliEnvironment: (command: AllureCliActiveCommand) => Record<string, string>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
export const ALLURE_CLI_ACTIVE_COMMAND_ENV = "ALLURE_CLI_ACTIVE_COMMAND";
|
|
3
|
+
export const getActiveAllureCliCommand = () => {
|
|
4
|
+
const activeCommand = process.env[ALLURE_CLI_ACTIVE_COMMAND_ENV];
|
|
5
|
+
return activeCommand === "agent" || activeCommand === "run" ? activeCommand : undefined;
|
|
6
|
+
};
|
|
7
|
+
export const createChildAllureCliEnvironment = (command) => ({
|
|
8
|
+
[ALLURE_CLI_ACTIVE_COMMAND_ENV]: command,
|
|
9
|
+
});
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
package/dist/utils/process.js
CHANGED
|
@@ -18,12 +18,13 @@ export const runProcess = (params) => {
|
|
|
18
18
|
COLORTERM: "truecolor",
|
|
19
19
|
TERM: "xterm-256color",
|
|
20
20
|
});
|
|
21
|
+
delete env.NO_COLOR;
|
|
21
22
|
}
|
|
22
23
|
return spawn(command, commandArgs, {
|
|
23
24
|
env,
|
|
24
25
|
cwd,
|
|
25
26
|
stdio: logs,
|
|
26
|
-
shell:
|
|
27
|
+
shell: IS_WIN,
|
|
27
28
|
});
|
|
28
29
|
};
|
|
29
30
|
export const terminationOf = (testProcess) => new Promise((resolve) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "allure",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Allure Commandline Tool",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"allure",
|
|
@@ -32,25 +32,26 @@
|
|
|
32
32
|
"lint:fix": "oxlint --import-plugin --fix src test features stories"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@allurereport/charts-api": "3.
|
|
36
|
-
"@allurereport/ci": "3.
|
|
37
|
-
"@allurereport/core": "3.
|
|
38
|
-
"@allurereport/core-api": "3.
|
|
39
|
-
"@allurereport/directory-watcher": "3.
|
|
40
|
-
"@allurereport/plugin-
|
|
41
|
-
"@allurereport/plugin-
|
|
42
|
-
"@allurereport/plugin-
|
|
43
|
-
"@allurereport/plugin-
|
|
44
|
-
"@allurereport/plugin-
|
|
45
|
-
"@allurereport/plugin-
|
|
46
|
-
"@allurereport/plugin-
|
|
47
|
-
"@allurereport/plugin-
|
|
48
|
-
"@allurereport/plugin-
|
|
49
|
-
"@allurereport/plugin-
|
|
50
|
-
"@allurereport/plugin-
|
|
51
|
-
"@allurereport/
|
|
52
|
-
"@allurereport/
|
|
53
|
-
"@allurereport/
|
|
35
|
+
"@allurereport/charts-api": "3.6.0",
|
|
36
|
+
"@allurereport/ci": "3.6.0",
|
|
37
|
+
"@allurereport/core": "3.6.0",
|
|
38
|
+
"@allurereport/core-api": "3.6.0",
|
|
39
|
+
"@allurereport/directory-watcher": "3.6.0",
|
|
40
|
+
"@allurereport/plugin-agent": "3.6.0",
|
|
41
|
+
"@allurereport/plugin-allure2": "3.6.0",
|
|
42
|
+
"@allurereport/plugin-api": "3.6.0",
|
|
43
|
+
"@allurereport/plugin-awesome": "3.6.0",
|
|
44
|
+
"@allurereport/plugin-classic": "3.6.0",
|
|
45
|
+
"@allurereport/plugin-csv": "3.6.0",
|
|
46
|
+
"@allurereport/plugin-dashboard": "3.6.0",
|
|
47
|
+
"@allurereport/plugin-jira": "3.6.0",
|
|
48
|
+
"@allurereport/plugin-log": "3.6.0",
|
|
49
|
+
"@allurereport/plugin-progress": "3.6.0",
|
|
50
|
+
"@allurereport/plugin-server-reload": "3.6.0",
|
|
51
|
+
"@allurereport/plugin-slack": "3.6.0",
|
|
52
|
+
"@allurereport/reader-api": "3.6.0",
|
|
53
|
+
"@allurereport/service": "3.6.0",
|
|
54
|
+
"@allurereport/static-server": "3.6.0",
|
|
54
55
|
"adm-zip": "^0.5.16",
|
|
55
56
|
"clipanion": "^4.0.0-rc.4",
|
|
56
57
|
"glob": "^11.1.0",
|