qagentic-reporter 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.
package/dist/index.js ADDED
@@ -0,0 +1,948 @@
1
+ 'use strict';
2
+
3
+ var fs3 = require('fs');
4
+ var path2 = require('path');
5
+ var uuid = require('uuid');
6
+ var axios = require('axios');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ function _interopNamespace(e) {
11
+ if (e && e.__esModule) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ var fs3__namespace = /*#__PURE__*/_interopNamespace(fs3);
29
+ var path2__namespace = /*#__PURE__*/_interopNamespace(path2);
30
+ var axios__default = /*#__PURE__*/_interopDefault(axios);
31
+
32
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
33
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
34
+ }) : x)(function(x) {
35
+ if (typeof require !== "undefined") return require.apply(this, arguments);
36
+ throw Error('Dynamic require of "' + x + '" is not supported');
37
+ });
38
+ var defaultConfig = {
39
+ projectName: "default",
40
+ environment: "local",
41
+ api: {
42
+ enabled: true,
43
+ url: "http://localhost:8080",
44
+ timeout: 3e4,
45
+ retryCount: 3,
46
+ batchSize: 100
47
+ },
48
+ local: {
49
+ enabled: true,
50
+ outputDir: "./qagentic-results",
51
+ formats: ["json", "html"],
52
+ cleanOnStart: true
53
+ },
54
+ features: {
55
+ aiAnalysis: true,
56
+ failureClustering: true,
57
+ flakyDetection: true,
58
+ screenshots: "on_failure",
59
+ videos: "on_failure",
60
+ consoleOutput: true
61
+ },
62
+ labels: {
63
+ custom: {}
64
+ }
65
+ };
66
+ var globalConfig = { ...defaultConfig };
67
+ function loadFromEnv(config) {
68
+ const env = process.env;
69
+ if (env.QAGENTIC_PROJECT_NAME) {
70
+ config.projectName = env.QAGENTIC_PROJECT_NAME;
71
+ }
72
+ if (env.QAGENTIC_ENVIRONMENT) {
73
+ config.environment = env.QAGENTIC_ENVIRONMENT;
74
+ }
75
+ if (env.QAGENTIC_API_ENABLED !== void 0) {
76
+ config.api.enabled = env.QAGENTIC_API_ENABLED.toLowerCase() === "true";
77
+ }
78
+ if (env.QAGENTIC_API_URL) {
79
+ config.api.url = env.QAGENTIC_API_URL;
80
+ }
81
+ if (env.QAGENTIC_API_KEY) {
82
+ config.api.key = env.QAGENTIC_API_KEY;
83
+ }
84
+ if (env.QAGENTIC_LOCAL_ENABLED !== void 0) {
85
+ config.local.enabled = env.QAGENTIC_LOCAL_ENABLED.toLowerCase() === "true";
86
+ }
87
+ if (env.QAGENTIC_OUTPUT_DIR) {
88
+ config.local.outputDir = env.QAGENTIC_OUTPUT_DIR;
89
+ }
90
+ if (env.QAGENTIC_OUTPUT_FORMAT) {
91
+ config.local.formats = env.QAGENTIC_OUTPUT_FORMAT.split(",").map(
92
+ (f) => f.trim()
93
+ );
94
+ }
95
+ if (env.QAGENTIC_AI_ANALYSIS !== void 0) {
96
+ config.features.aiAnalysis = env.QAGENTIC_AI_ANALYSIS.toLowerCase() === "true";
97
+ }
98
+ if (env.QAGENTIC_SCREENSHOTS) {
99
+ config.features.screenshots = env.QAGENTIC_SCREENSHOTS;
100
+ }
101
+ if (env.QAGENTIC_VIDEOS) {
102
+ config.features.videos = env.QAGENTIC_VIDEOS;
103
+ }
104
+ if (env.QAGENTIC_TEAM) {
105
+ config.labels.team = env.QAGENTIC_TEAM;
106
+ }
107
+ if (env.QAGENTIC_COMPONENT) {
108
+ config.labels.component = env.QAGENTIC_COMPONENT;
109
+ }
110
+ return config;
111
+ }
112
+ function loadFromFile(filePath, config) {
113
+ try {
114
+ if (!fs3__namespace.existsSync(filePath)) {
115
+ return config;
116
+ }
117
+ const content = fs3__namespace.readFileSync(filePath, "utf-8");
118
+ const yaml = __require("yaml");
119
+ const data = yaml.parse(content);
120
+ if (data.project) {
121
+ if (data.project.name) config.projectName = data.project.name;
122
+ if (data.project.environment) config.environment = data.project.environment;
123
+ }
124
+ if (data.reporting?.api) {
125
+ if (data.reporting.api.enabled !== void 0) config.api.enabled = data.reporting.api.enabled;
126
+ if (data.reporting.api.url) config.api.url = data.reporting.api.url;
127
+ if (data.reporting.api.key) config.api.key = data.reporting.api.key;
128
+ }
129
+ if (data.reporting?.local) {
130
+ if (data.reporting.local.enabled !== void 0)
131
+ config.local.enabled = data.reporting.local.enabled;
132
+ if (data.reporting.local.output_dir) config.local.outputDir = data.reporting.local.output_dir;
133
+ if (data.reporting.local.formats) config.local.formats = data.reporting.local.formats;
134
+ }
135
+ if (data.features) {
136
+ if (data.features.ai_analysis !== void 0)
137
+ config.features.aiAnalysis = data.features.ai_analysis;
138
+ if (data.features.failure_clustering !== void 0)
139
+ config.features.failureClustering = data.features.failure_clustering;
140
+ if (data.features.flaky_detection !== void 0)
141
+ config.features.flakyDetection = data.features.flaky_detection;
142
+ if (data.features.screenshots) config.features.screenshots = data.features.screenshots;
143
+ if (data.features.videos) config.features.videos = data.features.videos;
144
+ }
145
+ if (data.labels) {
146
+ if (data.labels.team) config.labels.team = data.labels.team;
147
+ if (data.labels.component) config.labels.component = data.labels.component;
148
+ config.labels.custom = { ...config.labels.custom, ...data.labels };
149
+ }
150
+ } catch (error) {
151
+ console.warn(`Failed to load config from ${filePath}:`, error);
152
+ }
153
+ return config;
154
+ }
155
+ function autoDiscover() {
156
+ const config = { ...defaultConfig };
157
+ const cwd = process.cwd();
158
+ const searchPaths = [
159
+ path2__namespace.join(cwd, "qagentic.yaml"),
160
+ path2__namespace.join(cwd, "qagentic.yml"),
161
+ path2__namespace.join(cwd, ".qagentic.yaml"),
162
+ path2__namespace.join(cwd, ".qagentic.yml")
163
+ ];
164
+ for (const filePath of searchPaths) {
165
+ if (fs3__namespace.existsSync(filePath)) {
166
+ loadFromFile(filePath, config);
167
+ break;
168
+ }
169
+ }
170
+ return loadFromEnv(config);
171
+ }
172
+ function configure(options = {}) {
173
+ globalConfig = autoDiscover();
174
+ if (options.projectName) {
175
+ globalConfig.projectName = options.projectName;
176
+ }
177
+ if (options.environment) {
178
+ globalConfig.environment = options.environment;
179
+ }
180
+ if (options.apiUrl) {
181
+ globalConfig.api.url = options.apiUrl;
182
+ }
183
+ if (options.apiKey) {
184
+ globalConfig.api.key = options.apiKey;
185
+ }
186
+ if (options.outputDir) {
187
+ globalConfig.local.outputDir = options.outputDir;
188
+ }
189
+ if (options.api) {
190
+ globalConfig.api = { ...globalConfig.api, ...options.api };
191
+ }
192
+ if (options.local) {
193
+ globalConfig.local = { ...globalConfig.local, ...options.local };
194
+ }
195
+ if (options.features) {
196
+ globalConfig.features = { ...globalConfig.features, ...options.features };
197
+ }
198
+ if (options.labels) {
199
+ globalConfig.labels = { ...globalConfig.labels, ...options.labels };
200
+ }
201
+ return globalConfig;
202
+ }
203
+ function getConfig() {
204
+ return globalConfig;
205
+ }
206
+
207
+ // src/core/severity.ts
208
+ var Severity = /* @__PURE__ */ ((Severity3) => {
209
+ Severity3["BLOCKER"] = "blocker";
210
+ Severity3["CRITICAL"] = "critical";
211
+ Severity3["NORMAL"] = "normal";
212
+ Severity3["MINOR"] = "minor";
213
+ Severity3["TRIVIAL"] = "trivial";
214
+ return Severity3;
215
+ })(Severity || {});
216
+ function parseSeverity(value) {
217
+ const normalized = value.toLowerCase();
218
+ switch (normalized) {
219
+ case "blocker":
220
+ return "blocker" /* BLOCKER */;
221
+ case "critical":
222
+ return "critical" /* CRITICAL */;
223
+ case "minor":
224
+ return "minor" /* MINOR */;
225
+ case "trivial":
226
+ return "trivial" /* TRIVIAL */;
227
+ default:
228
+ return "normal" /* NORMAL */;
229
+ }
230
+ }
231
+
232
+ // src/core/status.ts
233
+ var Status = /* @__PURE__ */ ((Status2) => {
234
+ Status2["PASSED"] = "passed";
235
+ Status2["FAILED"] = "failed";
236
+ Status2["BROKEN"] = "broken";
237
+ Status2["SKIPPED"] = "skipped";
238
+ Status2["PENDING"] = "pending";
239
+ Status2["RUNNING"] = "running";
240
+ Status2["UNKNOWN"] = "unknown";
241
+ return Status2;
242
+ })(Status || {});
243
+ var currentSteps = [];
244
+ function getCurrentStep() {
245
+ return currentSteps[currentSteps.length - 1];
246
+ }
247
+ var Step = class {
248
+ constructor(name, description2, parameters) {
249
+ this.status = "pending" /* PENDING */;
250
+ this.durationMs = 0;
251
+ this.attachments = [];
252
+ this.children = [];
253
+ this.parameters = {};
254
+ this.id = uuid.v4();
255
+ this.name = name;
256
+ this.description = description2;
257
+ this.parameters = parameters || {};
258
+ }
259
+ /**
260
+ * Start the step.
261
+ */
262
+ start() {
263
+ this.startTime = /* @__PURE__ */ new Date();
264
+ this.status = "running" /* RUNNING */;
265
+ currentSteps.push(this);
266
+ return this;
267
+ }
268
+ /**
269
+ * End the step.
270
+ */
271
+ end(error) {
272
+ this.endTime = /* @__PURE__ */ new Date();
273
+ if (this.startTime) {
274
+ this.durationMs = this.endTime.getTime() - this.startTime.getTime();
275
+ }
276
+ const index = currentSteps.indexOf(this);
277
+ if (index > -1) {
278
+ currentSteps.splice(index, 1);
279
+ }
280
+ const parent = getCurrentStep();
281
+ if (parent) {
282
+ parent.children.push(this);
283
+ }
284
+ if (error) {
285
+ this.status = "failed" /* FAILED */;
286
+ this.error = error.message;
287
+ this.errorTrace = error.stack;
288
+ } else {
289
+ this.status = "passed" /* PASSED */;
290
+ }
291
+ return this;
292
+ }
293
+ /**
294
+ * Attach data to this step.
295
+ */
296
+ attach(data, name, type = "text/plain") {
297
+ const content = typeof data === "string" ? data : data.toString("base64");
298
+ this.attachments.push({
299
+ id: uuid.v4(),
300
+ name,
301
+ type,
302
+ content,
303
+ size: typeof data === "string" ? data.length : data.length,
304
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
305
+ });
306
+ return this;
307
+ }
308
+ /**
309
+ * Attach a screenshot.
310
+ */
311
+ attachScreenshot(path4, name = "Screenshot") {
312
+ return this.attach(path4, name, "image/png");
313
+ }
314
+ /**
315
+ * Attach JSON data.
316
+ */
317
+ attachJson(data, name = "JSON Data") {
318
+ return this.attach(JSON.stringify(data, null, 2), name, "application/json");
319
+ }
320
+ /**
321
+ * Set a step parameter.
322
+ */
323
+ setParameter(name, value) {
324
+ this.parameters[name] = value;
325
+ return this;
326
+ }
327
+ /**
328
+ * Convert to result object.
329
+ */
330
+ toResult() {
331
+ return {
332
+ id: this.id,
333
+ name: this.name,
334
+ status: this.status,
335
+ startTime: this.startTime,
336
+ endTime: this.endTime,
337
+ durationMs: this.durationMs,
338
+ error: this.error,
339
+ errorTrace: this.errorTrace,
340
+ attachments: this.attachments,
341
+ children: this.children.map((c) => c.toResult()),
342
+ parameters: this.parameters
343
+ };
344
+ }
345
+ };
346
+ function step(name, descriptionOrFn, fn) {
347
+ const description2 = typeof descriptionOrFn === "string" ? descriptionOrFn : void 0;
348
+ const callback = typeof descriptionOrFn === "function" ? descriptionOrFn : fn;
349
+ const s = new Step(name, description2);
350
+ s.start();
351
+ try {
352
+ const result = callback();
353
+ if (result instanceof Promise) {
354
+ return result.then((value) => {
355
+ s.end();
356
+ return value;
357
+ }).catch((error) => {
358
+ s.end(error);
359
+ throw error;
360
+ });
361
+ }
362
+ s.end();
363
+ return result;
364
+ } catch (error) {
365
+ s.end(error);
366
+ throw error;
367
+ }
368
+ }
369
+
370
+ // src/core/decorators.ts
371
+ var QAGENTIC_METADATA = /* @__PURE__ */ Symbol("qagentic_metadata");
372
+ function getMetadata(target) {
373
+ const obj = target;
374
+ if (!obj[QAGENTIC_METADATA]) {
375
+ obj[QAGENTIC_METADATA] = {
376
+ labels: {},
377
+ links: [],
378
+ attachments: []
379
+ };
380
+ }
381
+ return obj[QAGENTIC_METADATA];
382
+ }
383
+ function addLabel(name, value) {
384
+ return function(target) {
385
+ const metadata = getMetadata(target);
386
+ metadata.labels[name] = value;
387
+ return target;
388
+ };
389
+ }
390
+ function feature(name) {
391
+ return addLabel("feature", name);
392
+ }
393
+ function story(name) {
394
+ return addLabel("story", name);
395
+ }
396
+ function epic(name) {
397
+ return addLabel("epic", name);
398
+ }
399
+ function severity(level) {
400
+ const sev = typeof level === "string" ? parseSeverity(level) : level;
401
+ return addLabel("severity", sev);
402
+ }
403
+ function tag(...tags) {
404
+ return function(target) {
405
+ const metadata = getMetadata(target);
406
+ const existingTags = metadata.labels["tags"] || [];
407
+ metadata.labels["tags"] = [...existingTags, ...tags];
408
+ return target;
409
+ };
410
+ }
411
+ function label(name, value) {
412
+ return addLabel(name, value);
413
+ }
414
+ function link(url, name, type = "link") {
415
+ return function(target) {
416
+ const metadata = getMetadata(target);
417
+ metadata.links.push({
418
+ url,
419
+ name: name || url,
420
+ type
421
+ });
422
+ return target;
423
+ };
424
+ }
425
+ function issue(url, name) {
426
+ return link(url, name, "issue");
427
+ }
428
+ function testcase(url, name) {
429
+ return link(url, name, "tms");
430
+ }
431
+ function description(text) {
432
+ return addLabel("description", text);
433
+ }
434
+ function title(name) {
435
+ return addLabel("title", name);
436
+ }
437
+ function owner(name) {
438
+ return addLabel("owner", name);
439
+ }
440
+ function layer(name) {
441
+ return addLabel("layer", name);
442
+ }
443
+ function suite(name) {
444
+ return addLabel("suite", name);
445
+ }
446
+ function subSuite(name) {
447
+ return addLabel("subSuite", name);
448
+ }
449
+ function parentSuite(name) {
450
+ return addLabel("parentSuite", name);
451
+ }
452
+ function attach(data, name, type = "text/plain", extension) {
453
+ const attachmentId = uuid.v4();
454
+ let content;
455
+ let size;
456
+ if (Buffer.isBuffer(data)) {
457
+ content = data.toString("base64");
458
+ size = data.length;
459
+ } else {
460
+ content = data;
461
+ size = data.length;
462
+ }
463
+ const attachment = {
464
+ id: attachmentId,
465
+ name,
466
+ type,
467
+ extension,
468
+ content,
469
+ size,
470
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
471
+ };
472
+ const currentStep = getCurrentStep();
473
+ if (currentStep) {
474
+ currentStep.attachments.push(attachment);
475
+ }
476
+ return attachmentId;
477
+ }
478
+ function attachFile(filePath, name) {
479
+ const resolvedPath = path2__namespace.resolve(filePath);
480
+ const displayName = name || path2__namespace.basename(filePath);
481
+ if (!fs3__namespace.existsSync(resolvedPath)) {
482
+ return attach(filePath, displayName, "text/plain");
483
+ }
484
+ const content = fs3__namespace.readFileSync(resolvedPath);
485
+ const ext = path2__namespace.extname(filePath).slice(1);
486
+ const mimeType = getMimeType(ext);
487
+ return attach(content, displayName, mimeType, ext);
488
+ }
489
+ function attachScreenshot(data, name = "Screenshot") {
490
+ if (typeof data === "string" && fs3__namespace.existsSync(data)) {
491
+ const content = fs3__namespace.readFileSync(data);
492
+ return attach(content, name, "image/png", "png");
493
+ }
494
+ return attach(data, name, "image/png", "png");
495
+ }
496
+ function attachJson(data, name = "JSON Data") {
497
+ const jsonStr = JSON.stringify(data, null, 2);
498
+ return attach(jsonStr, name, "application/json", "json");
499
+ }
500
+ function attachText(text, name = "Text") {
501
+ return attach(text, name, "text/plain", "txt");
502
+ }
503
+ function attachHtml(html, name = "HTML") {
504
+ return attach(html, name, "text/html", "html");
505
+ }
506
+ function attachVideo(filePath, name = "Video") {
507
+ const resolvedPath = path2__namespace.resolve(filePath);
508
+ if (!fs3__namespace.existsSync(resolvedPath)) {
509
+ return attach(filePath, name, "video/mp4", "mp4");
510
+ }
511
+ const content = fs3__namespace.readFileSync(resolvedPath);
512
+ const ext = path2__namespace.extname(filePath).slice(1);
513
+ const mimeType = ext === "webm" ? "video/webm" : "video/mp4";
514
+ return attach(content, name, mimeType, ext);
515
+ }
516
+ function getMimeType(ext) {
517
+ const mimeTypes = {
518
+ png: "image/png",
519
+ jpg: "image/jpeg",
520
+ jpeg: "image/jpeg",
521
+ gif: "image/gif",
522
+ webp: "image/webp",
523
+ svg: "image/svg+xml",
524
+ pdf: "application/pdf",
525
+ json: "application/json",
526
+ xml: "application/xml",
527
+ html: "text/html",
528
+ txt: "text/plain",
529
+ csv: "text/csv",
530
+ mp4: "video/mp4",
531
+ webm: "video/webm",
532
+ log: "text/plain"
533
+ };
534
+ return mimeTypes[ext.toLowerCase()] || "application/octet-stream";
535
+ }
536
+ var ConsoleReporter = class {
537
+ constructor(config) {
538
+ this.config = config || getConfig();
539
+ }
540
+ startRun(run) {
541
+ console.log("\n" + "=".repeat(60));
542
+ console.log(`\u{1F680} QAagentic Test Run - ${run.projectName}`);
543
+ console.log(`Environment: ${run.environment}`);
544
+ console.log("=".repeat(60) + "\n");
545
+ }
546
+ endRun(run) {
547
+ const icon = run.failed === 0 ? "\u2705" : "\u274C";
548
+ console.log("\n" + "=".repeat(60));
549
+ console.log(`${icon} Test Run Complete - ${run.passRate.toFixed(1)}% Pass Rate`);
550
+ console.log(`Passed: ${run.passed} | Failed: ${run.failed} | Skipped: ${run.skipped}`);
551
+ console.log("=".repeat(60) + "\n");
552
+ }
553
+ reportTest(test) {
554
+ if (!this.config.features.consoleOutput) return;
555
+ const symbols = {
556
+ ["passed" /* PASSED */]: "\u2713",
557
+ ["failed" /* FAILED */]: "\u2717",
558
+ ["broken" /* BROKEN */]: "!",
559
+ ["skipped" /* SKIPPED */]: "\u25CB",
560
+ ["pending" /* PENDING */]: "\u2026",
561
+ ["running" /* RUNNING */]: "\u2192",
562
+ ["unknown" /* UNKNOWN */]: "?"
563
+ };
564
+ const symbol = symbols[test.status] || "?";
565
+ console.log(` ${symbol} ${test.name}`);
566
+ if (test.errorMessage) {
567
+ console.log(` Error: ${test.errorMessage.slice(0, 100)}...`);
568
+ }
569
+ }
570
+ };
571
+ var JSONReporter = class {
572
+ constructor(config) {
573
+ this.config = config || getConfig();
574
+ this.outputDir = this.config.local.outputDir;
575
+ }
576
+ startRun(run) {
577
+ if (!fs3__namespace.existsSync(this.outputDir)) {
578
+ fs3__namespace.mkdirSync(this.outputDir, { recursive: true });
579
+ }
580
+ if (this.config.local.cleanOnStart) {
581
+ const files = fs3__namespace.readdirSync(this.outputDir);
582
+ for (const file of files) {
583
+ if (file.endsWith(".json")) {
584
+ fs3__namespace.unlinkSync(path2__namespace.join(this.outputDir, file));
585
+ }
586
+ }
587
+ }
588
+ }
589
+ endRun(run) {
590
+ if (!this.config.local.enabled || !this.config.local.formats.includes("json")) {
591
+ return;
592
+ }
593
+ const runFile = path2__namespace.join(this.outputDir, "run.json");
594
+ fs3__namespace.writeFileSync(runFile, JSON.stringify(this.serializeRun(run), null, 2));
595
+ const testsDir = path2__namespace.join(this.outputDir, "tests");
596
+ if (!fs3__namespace.existsSync(testsDir)) {
597
+ fs3__namespace.mkdirSync(testsDir, { recursive: true });
598
+ }
599
+ for (const test of run.tests) {
600
+ const testFile = path2__namespace.join(testsDir, `${test.id}.json`);
601
+ fs3__namespace.writeFileSync(testFile, JSON.stringify(this.serializeTest(test), null, 2));
602
+ }
603
+ }
604
+ reportTest(_test) {
605
+ }
606
+ serializeRun(run) {
607
+ return {
608
+ ...run,
609
+ startTime: run.startTime?.toISOString(),
610
+ endTime: run.endTime?.toISOString(),
611
+ tests: run.tests.map((t) => this.serializeTest(t))
612
+ };
613
+ }
614
+ serializeTest(test) {
615
+ return {
616
+ ...test,
617
+ startTime: test.startTime?.toISOString(),
618
+ endTime: test.endTime?.toISOString(),
619
+ steps: test.steps.map((s) => this.serializeStep(s))
620
+ };
621
+ }
622
+ serializeStep(step2) {
623
+ return {
624
+ ...step2,
625
+ startTime: step2.startTime?.toISOString(),
626
+ endTime: step2.endTime?.toISOString(),
627
+ children: step2.children.map((c) => this.serializeStep(c))
628
+ };
629
+ }
630
+ };
631
+ var JUnitReporter = class {
632
+ constructor(config) {
633
+ this.config = config || getConfig();
634
+ this.outputDir = this.config.local.outputDir;
635
+ }
636
+ startRun(_run) {
637
+ if (!fs3__namespace.existsSync(this.outputDir)) {
638
+ fs3__namespace.mkdirSync(this.outputDir, { recursive: true });
639
+ }
640
+ }
641
+ endRun(run) {
642
+ if (!this.config.local.enabled || !this.config.local.formats.includes("junit")) {
643
+ return;
644
+ }
645
+ const xml = this.generateXml(run);
646
+ const junitFile = path2__namespace.join(this.outputDir, "junit.xml");
647
+ fs3__namespace.writeFileSync(junitFile, xml);
648
+ }
649
+ reportTest(_test) {
650
+ }
651
+ generateXml(run) {
652
+ const escape = (str) => str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
653
+ let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
654
+ xml += `<testsuite name="${escape(run.projectName)}" `;
655
+ xml += `tests="${run.total}" `;
656
+ xml += `failures="${run.failed}" `;
657
+ xml += `errors="${run.broken}" `;
658
+ xml += `skipped="${run.skipped}" `;
659
+ xml += `time="${(run.durationMs / 1e3).toFixed(3)}" `;
660
+ xml += `timestamp="${run.startTime?.toISOString() || ""}">
661
+ `;
662
+ for (const test of run.tests) {
663
+ xml += ` <testcase name="${escape(test.name)}" `;
664
+ xml += `classname="${escape(test.fullName)}" `;
665
+ xml += `time="${(test.durationMs / 1e3).toFixed(3)}"`;
666
+ if (test.status === "passed" /* PASSED */) {
667
+ xml += "/>\n";
668
+ } else if (test.status === "failed" /* FAILED */) {
669
+ xml += ">\n";
670
+ xml += ` <failure message="${escape(test.errorMessage || "Test failed")}" `;
671
+ xml += `type="${escape(test.errorType || "AssertionError")}">`;
672
+ if (test.stackTrace) {
673
+ xml += escape(test.stackTrace);
674
+ }
675
+ xml += "</failure>\n";
676
+ xml += " </testcase>\n";
677
+ } else if (test.status === "broken" /* BROKEN */) {
678
+ xml += ">\n";
679
+ xml += ` <error message="${escape(test.errorMessage || "Test error")}" `;
680
+ xml += `type="${escape(test.errorType || "Error")}">`;
681
+ if (test.stackTrace) {
682
+ xml += escape(test.stackTrace);
683
+ }
684
+ xml += "</error>\n";
685
+ xml += " </testcase>\n";
686
+ } else if (test.status === "skipped" /* SKIPPED */) {
687
+ xml += ">\n";
688
+ xml += ` <skipped${test.errorMessage ? ` message="${escape(test.errorMessage)}"` : ""}/>
689
+ `;
690
+ xml += " </testcase>\n";
691
+ } else {
692
+ xml += "/>\n";
693
+ }
694
+ }
695
+ xml += "</testsuite>\n";
696
+ return xml;
697
+ }
698
+ };
699
+ var APIReporter = class {
700
+ constructor(config) {
701
+ this.batch = [];
702
+ this.config = config || getConfig();
703
+ }
704
+ async startRun(run) {
705
+ if (!this.config.api.enabled) return;
706
+ this.currentRun = run;
707
+ this.batch = [];
708
+ try {
709
+ await axios__default.default.post(
710
+ `${this.config.api.url}/api/v1/runs`,
711
+ {
712
+ id: run.id,
713
+ name: run.name,
714
+ project_name: run.projectName,
715
+ environment: run.environment,
716
+ start_time: run.startTime?.toISOString(),
717
+ labels: run.labels,
718
+ ci_build_id: run.ciBuildId,
719
+ branch: run.branch,
720
+ commit_hash: run.commitHash
721
+ },
722
+ {
723
+ headers: {
724
+ "Content-Type": "application/json",
725
+ "X-API-Key": this.config.api.key || "",
726
+ "X-Project": this.config.projectName
727
+ },
728
+ timeout: this.config.api.timeout
729
+ }
730
+ );
731
+ } catch (error) {
732
+ console.warn("Warning: Failed to register run with API:", error);
733
+ }
734
+ }
735
+ async endRun(run) {
736
+ if (!this.config.api.enabled) return;
737
+ await this.flushBatch();
738
+ try {
739
+ await axios__default.default.patch(
740
+ `${this.config.api.url}/api/v1/runs/${run.id}`,
741
+ {
742
+ end_time: run.endTime?.toISOString(),
743
+ duration_ms: run.durationMs,
744
+ total: run.total,
745
+ passed: run.passed,
746
+ failed: run.failed,
747
+ broken: run.broken,
748
+ skipped: run.skipped,
749
+ status: "completed"
750
+ },
751
+ {
752
+ headers: {
753
+ "Content-Type": "application/json",
754
+ "X-API-Key": this.config.api.key || ""
755
+ },
756
+ timeout: this.config.api.timeout
757
+ }
758
+ );
759
+ } catch (error) {
760
+ console.warn("Warning: Failed to finalize run with API:", error);
761
+ }
762
+ }
763
+ async reportTest(test) {
764
+ if (!this.config.api.enabled) return;
765
+ this.batch.push(test);
766
+ if (this.batch.length >= this.config.api.batchSize) {
767
+ await this.flushBatch();
768
+ }
769
+ }
770
+ async flushBatch() {
771
+ if (this.batch.length === 0 || !this.currentRun) return;
772
+ try {
773
+ await axios__default.default.post(
774
+ `${this.config.api.url}/api/v1/runs/${this.currentRun.id}/results`,
775
+ this.batch.map((t) => ({
776
+ ...t,
777
+ startTime: t.startTime?.toISOString(),
778
+ endTime: t.endTime?.toISOString()
779
+ })),
780
+ {
781
+ headers: {
782
+ "Content-Type": "application/json",
783
+ "X-API-Key": this.config.api.key || ""
784
+ },
785
+ timeout: this.config.api.timeout
786
+ }
787
+ );
788
+ } catch (error) {
789
+ console.warn("Warning: Failed to send test results to API:", error);
790
+ } finally {
791
+ this.batch = [];
792
+ }
793
+ }
794
+ };
795
+ var _QAgenticReporter = class _QAgenticReporter {
796
+ constructor(config) {
797
+ this.reporters = [];
798
+ this.currentRun = null;
799
+ this.config = config || getConfig();
800
+ if (this.config.features.consoleOutput) {
801
+ this.reporters.push(new ConsoleReporter(this.config));
802
+ }
803
+ if (this.config.local.enabled) {
804
+ this.reporters.push(new JSONReporter(this.config));
805
+ if (this.config.local.formats.includes("junit")) {
806
+ this.reporters.push(new JUnitReporter(this.config));
807
+ }
808
+ }
809
+ if (this.config.api.enabled) {
810
+ this.reporters.push(new APIReporter(this.config));
811
+ }
812
+ }
813
+ /**
814
+ * Get singleton instance.
815
+ */
816
+ static getInstance(config) {
817
+ if (!_QAgenticReporter.instance) {
818
+ _QAgenticReporter.instance = new _QAgenticReporter(config);
819
+ }
820
+ return _QAgenticReporter.instance;
821
+ }
822
+ /**
823
+ * Reset singleton instance.
824
+ */
825
+ static reset() {
826
+ _QAgenticReporter.instance = null;
827
+ }
828
+ /**
829
+ * Start a new test run.
830
+ */
831
+ async startRun(options = {}) {
832
+ this.currentRun = {
833
+ id: options.id || uuid.v4(),
834
+ name: options.name || `run_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "")}`,
835
+ projectName: options.projectName || this.config.projectName,
836
+ environment: options.environment || this.config.environment,
837
+ startTime: /* @__PURE__ */ new Date(),
838
+ durationMs: 0,
839
+ tests: [],
840
+ total: 0,
841
+ passed: 0,
842
+ failed: 0,
843
+ broken: 0,
844
+ skipped: 0,
845
+ passRate: 0,
846
+ labels: { ...this.config.labels.custom, ...options.labels },
847
+ parameters: options.parameters || {},
848
+ ciBuildId: options.ciBuildId,
849
+ ciBuildUrl: options.ciBuildUrl,
850
+ branch: options.branch,
851
+ commitHash: options.commitHash
852
+ };
853
+ for (const reporter of this.reporters) {
854
+ await reporter.startRun(this.currentRun);
855
+ }
856
+ return this.currentRun;
857
+ }
858
+ /**
859
+ * End the current test run.
860
+ */
861
+ async endRun() {
862
+ if (!this.currentRun) return null;
863
+ this.currentRun.endTime = /* @__PURE__ */ new Date();
864
+ this.currentRun.durationMs = this.currentRun.endTime.getTime() - (this.currentRun.startTime?.getTime() || 0);
865
+ this.currentRun.passRate = this.currentRun.total > 0 ? this.currentRun.passed / this.currentRun.total * 100 : 0;
866
+ for (const reporter of this.reporters) {
867
+ await reporter.endRun(this.currentRun);
868
+ }
869
+ const run = this.currentRun;
870
+ this.currentRun = null;
871
+ return run;
872
+ }
873
+ /**
874
+ * Report a test result.
875
+ */
876
+ async reportTest(test) {
877
+ if (this.currentRun) {
878
+ this.currentRun.tests.push(test);
879
+ this.currentRun.total++;
880
+ switch (test.status) {
881
+ case "passed" /* PASSED */:
882
+ this.currentRun.passed++;
883
+ break;
884
+ case "failed" /* FAILED */:
885
+ this.currentRun.failed++;
886
+ break;
887
+ case "broken" /* BROKEN */:
888
+ this.currentRun.broken++;
889
+ break;
890
+ case "skipped" /* SKIPPED */:
891
+ this.currentRun.skipped++;
892
+ break;
893
+ }
894
+ }
895
+ for (const reporter of this.reporters) {
896
+ await reporter.reportTest(test);
897
+ }
898
+ }
899
+ /**
900
+ * Get the current test run.
901
+ */
902
+ getCurrentRun() {
903
+ return this.currentRun;
904
+ }
905
+ };
906
+ _QAgenticReporter.instance = null;
907
+ var QAgenticReporter = _QAgenticReporter;
908
+ function getReporter(config) {
909
+ return QAgenticReporter.getInstance(config);
910
+ }
911
+
912
+ // src/index.ts
913
+ var VERSION = "0.1.0";
914
+
915
+ exports.QAgenticReporter = QAgenticReporter;
916
+ exports.Severity = Severity;
917
+ exports.Status = Status;
918
+ exports.Step = Step;
919
+ exports.VERSION = VERSION;
920
+ exports.attach = attach;
921
+ exports.attachFile = attachFile;
922
+ exports.attachHtml = attachHtml;
923
+ exports.attachJson = attachJson;
924
+ exports.attachScreenshot = attachScreenshot;
925
+ exports.attachText = attachText;
926
+ exports.attachVideo = attachVideo;
927
+ exports.configure = configure;
928
+ exports.description = description;
929
+ exports.epic = epic;
930
+ exports.feature = feature;
931
+ exports.getConfig = getConfig;
932
+ exports.getReporter = getReporter;
933
+ exports.issue = issue;
934
+ exports.label = label;
935
+ exports.layer = layer;
936
+ exports.link = link;
937
+ exports.owner = owner;
938
+ exports.parentSuite = parentSuite;
939
+ exports.severity = severity;
940
+ exports.step = step;
941
+ exports.story = story;
942
+ exports.subSuite = subSuite;
943
+ exports.suite = suite;
944
+ exports.tag = tag;
945
+ exports.testcase = testcase;
946
+ exports.title = title;
947
+ //# sourceMappingURL=index.js.map
948
+ //# sourceMappingURL=index.js.map