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