@aqa-pulse/cli 0.1.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.
Files changed (37) hide show
  1. package/README.md +43 -0
  2. package/bin/aqa-pulse.js +49 -0
  3. package/dist/backend/generate-source-facts.d.ts +2 -0
  4. package/dist/backend/generate-source-facts.js +607 -0
  5. package/dist/backend/merge-reports.d.ts +19 -0
  6. package/dist/backend/merge-reports.js +314 -0
  7. package/dist/backend/upload-report-artifacts.d.ts +9 -0
  8. package/dist/backend/upload-report-artifacts.js +772 -0
  9. package/dist/backend/upload-report.d.ts +13 -0
  10. package/dist/backend/upload-report.js +338 -0
  11. package/dist/dashboard-utils.d.ts +437 -0
  12. package/dist/dashboard-utils.js +2627 -0
  13. package/dist/history-utils.d.ts +72 -0
  14. package/dist/history-utils.js +267 -0
  15. package/dist/shared/business-assumptions.d.ts +14 -0
  16. package/dist/shared/business-assumptions.js +61 -0
  17. package/dist/shared/dashboard-helpers.d.ts +63 -0
  18. package/dist/shared/dashboard-helpers.js +429 -0
  19. package/dist/shared/dashboard-metric-info.d.ts +61 -0
  20. package/dist/shared/dashboard-metric-info.js +15 -0
  21. package/dist/shared/error-utils.d.ts +1 -0
  22. package/dist/shared/error-utils.js +6 -0
  23. package/dist/shared/formatting.d.ts +3 -0
  24. package/dist/shared/formatting.js +42 -0
  25. package/dist/shared/i18n/ru.d.ts +558 -0
  26. package/dist/shared/i18n/ru.js +577 -0
  27. package/dist/shared/metric-info.d.ts +5 -0
  28. package/dist/shared/metric-info.js +210 -0
  29. package/dist/shared/navigation.d.ts +31 -0
  30. package/dist/shared/navigation.js +99 -0
  31. package/dist/shared/test-history-helpers.d.ts +51 -0
  32. package/dist/shared/test-history-helpers.js +294 -0
  33. package/dist/shared/test-history-metric-info.d.ts +17 -0
  34. package/dist/shared/test-history-metric-info.js +20 -0
  35. package/dist/shared/text-utils.d.ts +2 -0
  36. package/dist/shared/text-utils.js +15 -0
  37. package/package.json +37 -0
@@ -0,0 +1,314 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.mergeAqaPulseReports = mergeAqaPulseReports;
37
+ /**
38
+ * Назначение: объединяет несколько Playwright dashboard JSON reports в один report,
39
+ * чтобы CI мог отправлять агрегированный результат без локальных helper-скриптов.
40
+ */
41
+ const fs = __importStar(require("node:fs"));
42
+ const path = __importStar(require("node:path"));
43
+ const dashboard_utils_1 = require("../dashboard-utils");
44
+ const error_utils_1 = require("../shared/error-utils");
45
+ if (require.main === module) {
46
+ void main();
47
+ }
48
+ async function main() {
49
+ try {
50
+ const options = parseCliOptions(process.argv.slice(2));
51
+ const result = mergeAqaPulseReports(options);
52
+ if (result.missingInputs.length > 0) {
53
+ console.warn(`AQA Pulse merge: пропущено отсутствующих report: ${result.missingInputs.length}`);
54
+ for (const inputPath of result.missingInputs) {
55
+ console.warn(` - ${toPosixPath(path.relative(process.cwd(), inputPath))}`);
56
+ }
57
+ }
58
+ fs.mkdirSync(path.dirname(options.outputPath), { recursive: true });
59
+ fs.writeFileSync(options.outputPath, `${JSON.stringify(result.mergedReport, null, 2)}\n`, 'utf8');
60
+ console.log('AQA Pulse reports merged.');
61
+ console.log(`Output path: ${options.outputPath}`);
62
+ console.log(`Project kind: ${options.projectKind}`);
63
+ console.log(`Reports: ${result.reports.length}`);
64
+ console.log(`Tests: ${result.mergedReport.summary?.total ?? 0}, passed: ${result.mergedReport.summary?.passed ?? 0}, failed: ${result.mergedReport.summary?.failed ?? 0}, flaky: ${result.mergedReport.summary?.flaky ?? 0}`);
65
+ }
66
+ catch (error) {
67
+ console.error(`Ошибка merge reports: ${(0, error_utils_1.getErrorMessage)(error)}`);
68
+ process.exitCode = 1;
69
+ }
70
+ }
71
+ function mergeAqaPulseReports(options) {
72
+ const reports = [];
73
+ const missingInputs = [];
74
+ for (const inputPath of options.inputs) {
75
+ if (!fs.existsSync(inputPath)) {
76
+ if (options.allowMissing) {
77
+ missingInputs.push(inputPath);
78
+ continue;
79
+ }
80
+ throw new Error(`Не найден input report: ${inputPath}`);
81
+ }
82
+ const report = (0, dashboard_utils_1.loadReporterReport)(inputPath);
83
+ reports.push({
84
+ inputPath,
85
+ relativeInputPath: toPosixPath(path.relative(process.cwd(), inputPath)),
86
+ report,
87
+ sourceLabel: buildSourceLabel(inputPath),
88
+ });
89
+ }
90
+ if (reports.length === 0) {
91
+ throw new Error(`Не найден ни один input report. Проверено файлов: ${options.inputs.length}`);
92
+ }
93
+ validateReports(reports, options.projectKind);
94
+ const mergedTests = reports.flatMap(({ report, sourceLabel, inputPath }) => (report.tests ?? []).map((test, index) => ({
95
+ ...test,
96
+ id: buildMergedTestId(sourceLabel, test, index),
97
+ aqaPulseSourceReportPath: inputPath,
98
+ })));
99
+ return {
100
+ reports,
101
+ missingInputs,
102
+ mergedReport: {
103
+ schemaVersion: reports[0]?.report.schemaVersion,
104
+ timestamp: pickTimestamp(reports),
105
+ durationMs: sumNumbers(reports.map(({ report }) => report.durationMs)),
106
+ summary: buildSummary(mergedTests),
107
+ environment: buildEnvironment(reports, options.projectKind),
108
+ mergeMetadata: buildMergeMetadata(reports, options.projectKind),
109
+ tests: mergedTests,
110
+ },
111
+ };
112
+ }
113
+ function parseCliOptions(args) {
114
+ if (args.includes('--help') || args.includes('-h')) {
115
+ printHelp();
116
+ process.exit(0);
117
+ }
118
+ const options = {
119
+ inputs: [],
120
+ allowMissing: false,
121
+ };
122
+ const positionalArgs = [];
123
+ for (let index = 0; index < args.length; index += 1) {
124
+ const currentArg = args[index];
125
+ if (currentArg === '--allow-missing') {
126
+ options.allowMissing = true;
127
+ continue;
128
+ }
129
+ if (currentArg === '--project-kind') {
130
+ options.projectKind = readNextArg(args, index, currentArg);
131
+ index += 1;
132
+ continue;
133
+ }
134
+ if (currentArg === '--output') {
135
+ options.outputPath = path.resolve(process.cwd(), readNextArg(args, index, currentArg));
136
+ index += 1;
137
+ continue;
138
+ }
139
+ if (currentArg.startsWith('--')) {
140
+ throw new Error(`Неизвестный аргумент: ${currentArg}`);
141
+ }
142
+ positionalArgs.push(currentArg);
143
+ }
144
+ if (!options.projectKind && positionalArgs.length > 0) {
145
+ options.projectKind = positionalArgs.shift();
146
+ }
147
+ if (!options.outputPath && positionalArgs.length > 0) {
148
+ options.outputPath = path.resolve(process.cwd(), positionalArgs.shift() ?? '');
149
+ }
150
+ options.inputs.push(...positionalArgs.map((value) => path.resolve(process.cwd(), value)));
151
+ if (options.projectKind !== 'ui' && options.projectKind !== 'api') {
152
+ throw new Error('Аргумент --project-kind должен быть ui или api.');
153
+ }
154
+ if (!options.outputPath) {
155
+ throw new Error('Нужно передать --output <path>.');
156
+ }
157
+ if (options.inputs.length === 0) {
158
+ throw new Error('Нужно передать хотя бы один input report.');
159
+ }
160
+ return {
161
+ projectKind: options.projectKind,
162
+ outputPath: options.outputPath,
163
+ inputs: options.inputs,
164
+ allowMissing: options.allowMissing ?? false,
165
+ };
166
+ }
167
+ function readNextArg(args, index, label) {
168
+ const value = args[index + 1];
169
+ if (!value || value.startsWith('--')) {
170
+ throw new Error(`Для аргумента ${label} нужно передать значение.`);
171
+ }
172
+ return value;
173
+ }
174
+ function validateReports(reports, expectedProjectKind) {
175
+ const schemaVersions = new Set(reports.map(({ report }) => report.schemaVersion));
176
+ if (schemaVersions.size > 1) {
177
+ throw new Error(`Нельзя объединять reports с разными schemaVersion: ${Array.from(schemaVersions).join(', ')}`);
178
+ }
179
+ for (const { inputPath, report } of reports) {
180
+ for (const test of report.tests ?? []) {
181
+ const actualProjectKind = detectProjectKind(test);
182
+ if (actualProjectKind !== expectedProjectKind) {
183
+ throw new Error(`Файл ${inputPath} содержит тест другого project kind: ожидался ${expectedProjectKind}, получен ${actualProjectKind ?? '<unknown>'}. Тест: ${describeTest(test)}`);
184
+ }
185
+ }
186
+ }
187
+ }
188
+ function buildMergedTestId(sourceLabel, test, index) {
189
+ const baseId = pickText(test.id)
190
+ ?? `${pickText(test.project) ?? 'unknown-project'}:${pickText(test.location?.file) ?? 'unknown-file'}:${pickText(test.title) ?? 'unknown-title'}:${index}`;
191
+ return `${sourceLabel}::${baseId}`;
192
+ }
193
+ function buildSummary(tests) {
194
+ const summary = {
195
+ total: tests.length,
196
+ passed: 0,
197
+ failed: 0,
198
+ flaky: 0,
199
+ skipped: 0,
200
+ timedOut: 0,
201
+ interrupted: 0,
202
+ };
203
+ for (const test of tests) {
204
+ const status = normalizeStatus(test.status);
205
+ if (status === 'passed') {
206
+ summary.passed += 1;
207
+ }
208
+ else if (status === 'failed') {
209
+ summary.failed += 1;
210
+ }
211
+ else if (status === 'skipped') {
212
+ summary.skipped += 1;
213
+ }
214
+ else if (status === 'timedOut') {
215
+ summary.timedOut += 1;
216
+ }
217
+ else if (status === 'interrupted') {
218
+ summary.interrupted += 1;
219
+ }
220
+ if (test.flaky === true) {
221
+ summary.flaky += 1;
222
+ }
223
+ }
224
+ return summary;
225
+ }
226
+ function buildEnvironment(reports, projectKind) {
227
+ const firstEnvironment = reports[0]?.report.environment ?? {};
228
+ return {
229
+ ...firstEnvironment,
230
+ workers: pickMaxNumber(reports.map(({ report }) => report.environment?.workers)),
231
+ retries: pickMaxNumber(reports.map(({ report }) => report.environment?.retries)),
232
+ projects: [projectKind],
233
+ };
234
+ }
235
+ function buildMergeMetadata(reports, projectKind) {
236
+ return {
237
+ projectKind,
238
+ mergedAt: new Date().toISOString(),
239
+ sourceCount: reports.length,
240
+ sourceFiles: reports.map((reportEntry) => ({
241
+ sourceLabel: reportEntry.sourceLabel,
242
+ path: reportEntry.relativeInputPath,
243
+ timestamp: reportEntry.report.timestamp ?? null,
244
+ testsCount: reportEntry.report.tests?.length ?? 0,
245
+ summary: {
246
+ total: reportEntry.report.summary?.total ?? 0,
247
+ passed: reportEntry.report.summary?.passed ?? 0,
248
+ failed: reportEntry.report.summary?.failed ?? 0,
249
+ flaky: reportEntry.report.summary?.flaky ?? 0,
250
+ skipped: reportEntry.report.summary?.skipped ?? 0,
251
+ timedOut: reportEntry.report.summary?.timedOut ?? 0,
252
+ interrupted: reportEntry.report.summary?.interrupted ?? 0,
253
+ },
254
+ })),
255
+ };
256
+ }
257
+ function pickTimestamp(reports) {
258
+ return reports
259
+ .map(({ report }) => report.timestamp)
260
+ .filter((value) => typeof value === 'string' && value.trim().length > 0)
261
+ .sort()[0] ?? new Date().toISOString();
262
+ }
263
+ function sumNumbers(values) {
264
+ return values.reduce((accumulator, value) => accumulator + (typeof value === 'number' && Number.isFinite(value) ? value : 0), 0);
265
+ }
266
+ function pickMaxNumber(values) {
267
+ const numbers = values.filter((value) => typeof value === 'number' && Number.isFinite(value));
268
+ return numbers.length > 0 ? Math.max(...numbers) : undefined;
269
+ }
270
+ function normalizeStatus(value) {
271
+ const normalized = String(value ?? '').trim().toLowerCase();
272
+ if (normalized === 'timedout') {
273
+ return 'timedOut';
274
+ }
275
+ if (normalized === 'passed' || normalized === 'failed' || normalized === 'skipped' || normalized === 'interrupted') {
276
+ return normalized;
277
+ }
278
+ return value === 'timedOut' ? 'timedOut' : normalized;
279
+ }
280
+ function detectProjectKind(test) {
281
+ const project = pickText(test.project)?.toLowerCase();
282
+ if (project === 'ui' || project === 'api') {
283
+ return project;
284
+ }
285
+ const filePath = pickText(test.location?.file)?.replace(/\\/g, '/');
286
+ if (filePath?.includes('/tests/UI/') || filePath?.startsWith('tests/UI/')) {
287
+ return 'ui';
288
+ }
289
+ if (filePath?.includes('/tests/API/') || filePath?.startsWith('tests/API/')) {
290
+ return 'api';
291
+ }
292
+ return null;
293
+ }
294
+ function buildSourceLabel(inputPath) {
295
+ return path.basename(inputPath, path.extname(inputPath))
296
+ .replace(/[^a-zA-Z0-9_-]+/g, '-')
297
+ .replace(/^-+|-+$/g, '')
298
+ || 'report';
299
+ }
300
+ function describeTest(test) {
301
+ return `${pickText(test.project) ?? '<unknown-project>'} :: ${pickText(test.location?.file) ?? '<unknown-file>'} :: ${pickText(test.title) ?? '<unknown-title>'}`;
302
+ }
303
+ function pickText(value) {
304
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
305
+ }
306
+ function toPosixPath(value) {
307
+ return value.replace(/\\/g, '/');
308
+ }
309
+ function printHelp() {
310
+ console.log('aqa-pulse-server merge-reports [--project-kind ui|api] --output <path> [--allow-missing] <input...>');
311
+ console.log('');
312
+ console.log('Пример:');
313
+ console.log(' aqa-pulse-server merge-reports --project-kind ui --allow-missing --output test-results/dashboard/ui-merged.json test-results/dashboard/ui-*.json');
314
+ }
@@ -0,0 +1,9 @@
1
+ import type { ReporterRoot } from '../dashboard-utils';
2
+ interface ReportPreparationOptions {
3
+ reportPath: string;
4
+ preparedReportPath?: string | null;
5
+ debugAttachmentsSummary?: boolean;
6
+ inlineAttachmentsTotalMaxSizeBytes?: number | null;
7
+ }
8
+ export declare function prepareReporterReportForUpload(report: ReporterRoot, options: ReportPreparationOptions): ReporterRoot;
9
+ export {};