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,922 @@
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
+
193
+ // src/core/reporter.ts
194
+ var ConsoleReporter = class {
195
+ constructor(config) {
196
+ this.config = config || getConfig();
197
+ }
198
+ startRun(run) {
199
+ console.log("\n" + "=".repeat(60));
200
+ console.log(`\u{1F680} QAagentic Test Run - ${run.projectName}`);
201
+ console.log(`Environment: ${run.environment}`);
202
+ console.log("=".repeat(60) + "\n");
203
+ }
204
+ endRun(run) {
205
+ const icon = run.failed === 0 ? "\u2705" : "\u274C";
206
+ console.log("\n" + "=".repeat(60));
207
+ console.log(`${icon} Test Run Complete - ${run.passRate.toFixed(1)}% Pass Rate`);
208
+ console.log(`Passed: ${run.passed} | Failed: ${run.failed} | Skipped: ${run.skipped}`);
209
+ console.log("=".repeat(60) + "\n");
210
+ }
211
+ reportTest(test) {
212
+ if (!this.config.features.consoleOutput) return;
213
+ const symbols = {
214
+ ["passed" /* PASSED */]: "\u2713",
215
+ ["failed" /* FAILED */]: "\u2717",
216
+ ["broken" /* BROKEN */]: "!",
217
+ ["skipped" /* SKIPPED */]: "\u25CB",
218
+ ["pending" /* PENDING */]: "\u2026",
219
+ ["running" /* RUNNING */]: "\u2192",
220
+ ["unknown" /* UNKNOWN */]: "?"
221
+ };
222
+ const symbol = symbols[test.status] || "?";
223
+ console.log(` ${symbol} ${test.name}`);
224
+ if (test.errorMessage) {
225
+ console.log(` Error: ${test.errorMessage.slice(0, 100)}...`);
226
+ }
227
+ }
228
+ };
229
+ var JSONReporter = class {
230
+ constructor(config) {
231
+ this.config = config || getConfig();
232
+ this.outputDir = this.config.local.outputDir;
233
+ }
234
+ startRun(run) {
235
+ if (!fs2.existsSync(this.outputDir)) {
236
+ fs2.mkdirSync(this.outputDir, { recursive: true });
237
+ }
238
+ if (this.config.local.cleanOnStart) {
239
+ const files = fs2.readdirSync(this.outputDir);
240
+ for (const file of files) {
241
+ if (file.endsWith(".json")) {
242
+ fs2.unlinkSync(path2.join(this.outputDir, file));
243
+ }
244
+ }
245
+ }
246
+ }
247
+ endRun(run) {
248
+ if (!this.config.local.enabled || !this.config.local.formats.includes("json")) {
249
+ return;
250
+ }
251
+ const runFile = path2.join(this.outputDir, "run.json");
252
+ fs2.writeFileSync(runFile, JSON.stringify(this.serializeRun(run), null, 2));
253
+ const testsDir = path2.join(this.outputDir, "tests");
254
+ if (!fs2.existsSync(testsDir)) {
255
+ fs2.mkdirSync(testsDir, { recursive: true });
256
+ }
257
+ for (const test of run.tests) {
258
+ const testFile = path2.join(testsDir, `${test.id}.json`);
259
+ fs2.writeFileSync(testFile, JSON.stringify(this.serializeTest(test), null, 2));
260
+ }
261
+ }
262
+ reportTest(_test) {
263
+ }
264
+ serializeRun(run) {
265
+ return {
266
+ ...run,
267
+ startTime: run.startTime?.toISOString(),
268
+ endTime: run.endTime?.toISOString(),
269
+ tests: run.tests.map((t) => this.serializeTest(t))
270
+ };
271
+ }
272
+ serializeTest(test) {
273
+ return {
274
+ ...test,
275
+ startTime: test.startTime?.toISOString(),
276
+ endTime: test.endTime?.toISOString(),
277
+ steps: test.steps.map((s) => this.serializeStep(s))
278
+ };
279
+ }
280
+ serializeStep(step2) {
281
+ return {
282
+ ...step2,
283
+ startTime: step2.startTime?.toISOString(),
284
+ endTime: step2.endTime?.toISOString(),
285
+ children: step2.children.map((c) => this.serializeStep(c))
286
+ };
287
+ }
288
+ };
289
+ var JUnitReporter = class {
290
+ constructor(config) {
291
+ this.config = config || getConfig();
292
+ this.outputDir = this.config.local.outputDir;
293
+ }
294
+ startRun(_run) {
295
+ if (!fs2.existsSync(this.outputDir)) {
296
+ fs2.mkdirSync(this.outputDir, { recursive: true });
297
+ }
298
+ }
299
+ endRun(run) {
300
+ if (!this.config.local.enabled || !this.config.local.formats.includes("junit")) {
301
+ return;
302
+ }
303
+ const xml = this.generateXml(run);
304
+ const junitFile = path2.join(this.outputDir, "junit.xml");
305
+ fs2.writeFileSync(junitFile, xml);
306
+ }
307
+ reportTest(_test) {
308
+ }
309
+ generateXml(run) {
310
+ const escape = (str) => str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
311
+ let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
312
+ xml += `<testsuite name="${escape(run.projectName)}" `;
313
+ xml += `tests="${run.total}" `;
314
+ xml += `failures="${run.failed}" `;
315
+ xml += `errors="${run.broken}" `;
316
+ xml += `skipped="${run.skipped}" `;
317
+ xml += `time="${(run.durationMs / 1e3).toFixed(3)}" `;
318
+ xml += `timestamp="${run.startTime?.toISOString() || ""}">
319
+ `;
320
+ for (const test of run.tests) {
321
+ xml += ` <testcase name="${escape(test.name)}" `;
322
+ xml += `classname="${escape(test.fullName)}" `;
323
+ xml += `time="${(test.durationMs / 1e3).toFixed(3)}"`;
324
+ if (test.status === "passed" /* PASSED */) {
325
+ xml += "/>\n";
326
+ } else if (test.status === "failed" /* FAILED */) {
327
+ xml += ">\n";
328
+ xml += ` <failure message="${escape(test.errorMessage || "Test failed")}" `;
329
+ xml += `type="${escape(test.errorType || "AssertionError")}">`;
330
+ if (test.stackTrace) {
331
+ xml += escape(test.stackTrace);
332
+ }
333
+ xml += "</failure>\n";
334
+ xml += " </testcase>\n";
335
+ } else if (test.status === "broken" /* BROKEN */) {
336
+ xml += ">\n";
337
+ xml += ` <error message="${escape(test.errorMessage || "Test error")}" `;
338
+ xml += `type="${escape(test.errorType || "Error")}">`;
339
+ if (test.stackTrace) {
340
+ xml += escape(test.stackTrace);
341
+ }
342
+ xml += "</error>\n";
343
+ xml += " </testcase>\n";
344
+ } else if (test.status === "skipped" /* SKIPPED */) {
345
+ xml += ">\n";
346
+ xml += ` <skipped${test.errorMessage ? ` message="${escape(test.errorMessage)}"` : ""}/>
347
+ `;
348
+ xml += " </testcase>\n";
349
+ } else {
350
+ xml += "/>\n";
351
+ }
352
+ }
353
+ xml += "</testsuite>\n";
354
+ return xml;
355
+ }
356
+ };
357
+ var APIReporter = class {
358
+ constructor(config) {
359
+ this.batch = [];
360
+ this.config = config || getConfig();
361
+ }
362
+ async startRun(run) {
363
+ if (!this.config.api.enabled) return;
364
+ this.currentRun = run;
365
+ this.batch = [];
366
+ try {
367
+ await axios.post(
368
+ `${this.config.api.url}/api/v1/runs`,
369
+ {
370
+ id: run.id,
371
+ name: run.name,
372
+ project_name: run.projectName,
373
+ environment: run.environment,
374
+ start_time: run.startTime?.toISOString(),
375
+ labels: run.labels,
376
+ ci_build_id: run.ciBuildId,
377
+ branch: run.branch,
378
+ commit_hash: run.commitHash
379
+ },
380
+ {
381
+ headers: {
382
+ "Content-Type": "application/json",
383
+ "X-API-Key": this.config.api.key || "",
384
+ "X-Project": this.config.projectName
385
+ },
386
+ timeout: this.config.api.timeout
387
+ }
388
+ );
389
+ } catch (error) {
390
+ console.warn("Warning: Failed to register run with API:", error);
391
+ }
392
+ }
393
+ async endRun(run) {
394
+ if (!this.config.api.enabled) return;
395
+ await this.flushBatch();
396
+ try {
397
+ await axios.patch(
398
+ `${this.config.api.url}/api/v1/runs/${run.id}`,
399
+ {
400
+ end_time: run.endTime?.toISOString(),
401
+ duration_ms: run.durationMs,
402
+ total: run.total,
403
+ passed: run.passed,
404
+ failed: run.failed,
405
+ broken: run.broken,
406
+ skipped: run.skipped,
407
+ status: "completed"
408
+ },
409
+ {
410
+ headers: {
411
+ "Content-Type": "application/json",
412
+ "X-API-Key": this.config.api.key || ""
413
+ },
414
+ timeout: this.config.api.timeout
415
+ }
416
+ );
417
+ } catch (error) {
418
+ console.warn("Warning: Failed to finalize run with API:", error);
419
+ }
420
+ }
421
+ async reportTest(test) {
422
+ if (!this.config.api.enabled) return;
423
+ this.batch.push(test);
424
+ if (this.batch.length >= this.config.api.batchSize) {
425
+ await this.flushBatch();
426
+ }
427
+ }
428
+ async flushBatch() {
429
+ if (this.batch.length === 0 || !this.currentRun) return;
430
+ try {
431
+ await axios.post(
432
+ `${this.config.api.url}/api/v1/runs/${this.currentRun.id}/results`,
433
+ this.batch.map((t) => ({
434
+ ...t,
435
+ startTime: t.startTime?.toISOString(),
436
+ endTime: t.endTime?.toISOString()
437
+ })),
438
+ {
439
+ headers: {
440
+ "Content-Type": "application/json",
441
+ "X-API-Key": this.config.api.key || ""
442
+ },
443
+ timeout: this.config.api.timeout
444
+ }
445
+ );
446
+ } catch (error) {
447
+ console.warn("Warning: Failed to send test results to API:", error);
448
+ } finally {
449
+ this.batch = [];
450
+ }
451
+ }
452
+ };
453
+ var _QAgenticReporter = class _QAgenticReporter {
454
+ constructor(config) {
455
+ this.reporters = [];
456
+ this.currentRun = null;
457
+ this.config = config || getConfig();
458
+ if (this.config.features.consoleOutput) {
459
+ this.reporters.push(new ConsoleReporter(this.config));
460
+ }
461
+ if (this.config.local.enabled) {
462
+ this.reporters.push(new JSONReporter(this.config));
463
+ if (this.config.local.formats.includes("junit")) {
464
+ this.reporters.push(new JUnitReporter(this.config));
465
+ }
466
+ }
467
+ if (this.config.api.enabled) {
468
+ this.reporters.push(new APIReporter(this.config));
469
+ }
470
+ }
471
+ /**
472
+ * Get singleton instance.
473
+ */
474
+ static getInstance(config) {
475
+ if (!_QAgenticReporter.instance) {
476
+ _QAgenticReporter.instance = new _QAgenticReporter(config);
477
+ }
478
+ return _QAgenticReporter.instance;
479
+ }
480
+ /**
481
+ * Reset singleton instance.
482
+ */
483
+ static reset() {
484
+ _QAgenticReporter.instance = null;
485
+ }
486
+ /**
487
+ * Start a new test run.
488
+ */
489
+ async startRun(options = {}) {
490
+ this.currentRun = {
491
+ id: options.id || v4(),
492
+ name: options.name || `run_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "")}`,
493
+ projectName: options.projectName || this.config.projectName,
494
+ environment: options.environment || this.config.environment,
495
+ startTime: /* @__PURE__ */ new Date(),
496
+ durationMs: 0,
497
+ tests: [],
498
+ total: 0,
499
+ passed: 0,
500
+ failed: 0,
501
+ broken: 0,
502
+ skipped: 0,
503
+ passRate: 0,
504
+ labels: { ...this.config.labels.custom, ...options.labels },
505
+ parameters: options.parameters || {},
506
+ ciBuildId: options.ciBuildId,
507
+ ciBuildUrl: options.ciBuildUrl,
508
+ branch: options.branch,
509
+ commitHash: options.commitHash
510
+ };
511
+ for (const reporter of this.reporters) {
512
+ await reporter.startRun(this.currentRun);
513
+ }
514
+ return this.currentRun;
515
+ }
516
+ /**
517
+ * End the current test run.
518
+ */
519
+ async endRun() {
520
+ if (!this.currentRun) return null;
521
+ this.currentRun.endTime = /* @__PURE__ */ new Date();
522
+ this.currentRun.durationMs = this.currentRun.endTime.getTime() - (this.currentRun.startTime?.getTime() || 0);
523
+ this.currentRun.passRate = this.currentRun.total > 0 ? this.currentRun.passed / this.currentRun.total * 100 : 0;
524
+ for (const reporter of this.reporters) {
525
+ await reporter.endRun(this.currentRun);
526
+ }
527
+ const run = this.currentRun;
528
+ this.currentRun = null;
529
+ return run;
530
+ }
531
+ /**
532
+ * Report a test result.
533
+ */
534
+ async reportTest(test) {
535
+ if (this.currentRun) {
536
+ this.currentRun.tests.push(test);
537
+ this.currentRun.total++;
538
+ switch (test.status) {
539
+ case "passed" /* PASSED */:
540
+ this.currentRun.passed++;
541
+ break;
542
+ case "failed" /* FAILED */:
543
+ this.currentRun.failed++;
544
+ break;
545
+ case "broken" /* BROKEN */:
546
+ this.currentRun.broken++;
547
+ break;
548
+ case "skipped" /* SKIPPED */:
549
+ this.currentRun.skipped++;
550
+ break;
551
+ }
552
+ }
553
+ for (const reporter of this.reporters) {
554
+ await reporter.reportTest(test);
555
+ }
556
+ }
557
+ /**
558
+ * Get the current test run.
559
+ */
560
+ getCurrentRun() {
561
+ return this.currentRun;
562
+ }
563
+ };
564
+ _QAgenticReporter.instance = null;
565
+ var QAgenticReporter = _QAgenticReporter;
566
+ var currentSteps = [];
567
+ function getCurrentStep() {
568
+ return currentSteps[currentSteps.length - 1];
569
+ }
570
+ var Step = class {
571
+ constructor(name, description, parameters) {
572
+ this.status = "pending" /* PENDING */;
573
+ this.durationMs = 0;
574
+ this.attachments = [];
575
+ this.children = [];
576
+ this.parameters = {};
577
+ this.id = v4();
578
+ this.name = name;
579
+ this.description = description;
580
+ this.parameters = parameters || {};
581
+ }
582
+ /**
583
+ * Start the step.
584
+ */
585
+ start() {
586
+ this.startTime = /* @__PURE__ */ new Date();
587
+ this.status = "running" /* RUNNING */;
588
+ currentSteps.push(this);
589
+ return this;
590
+ }
591
+ /**
592
+ * End the step.
593
+ */
594
+ end(error) {
595
+ this.endTime = /* @__PURE__ */ new Date();
596
+ if (this.startTime) {
597
+ this.durationMs = this.endTime.getTime() - this.startTime.getTime();
598
+ }
599
+ const index = currentSteps.indexOf(this);
600
+ if (index > -1) {
601
+ currentSteps.splice(index, 1);
602
+ }
603
+ const parent = getCurrentStep();
604
+ if (parent) {
605
+ parent.children.push(this);
606
+ }
607
+ if (error) {
608
+ this.status = "failed" /* FAILED */;
609
+ this.error = error.message;
610
+ this.errorTrace = error.stack;
611
+ } else {
612
+ this.status = "passed" /* PASSED */;
613
+ }
614
+ return this;
615
+ }
616
+ /**
617
+ * Attach data to this step.
618
+ */
619
+ attach(data, name, type = "text/plain") {
620
+ const content = typeof data === "string" ? data : data.toString("base64");
621
+ this.attachments.push({
622
+ id: v4(),
623
+ name,
624
+ type,
625
+ content,
626
+ size: typeof data === "string" ? data.length : data.length,
627
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
628
+ });
629
+ return this;
630
+ }
631
+ /**
632
+ * Attach a screenshot.
633
+ */
634
+ attachScreenshot(path3, name = "Screenshot") {
635
+ return this.attach(path3, name, "image/png");
636
+ }
637
+ /**
638
+ * Attach JSON data.
639
+ */
640
+ attachJson(data, name = "JSON Data") {
641
+ return this.attach(JSON.stringify(data, null, 2), name, "application/json");
642
+ }
643
+ /**
644
+ * Set a step parameter.
645
+ */
646
+ setParameter(name, value) {
647
+ this.parameters[name] = value;
648
+ return this;
649
+ }
650
+ /**
651
+ * Convert to result object.
652
+ */
653
+ toResult() {
654
+ return {
655
+ id: this.id,
656
+ name: this.name,
657
+ status: this.status,
658
+ startTime: this.startTime,
659
+ endTime: this.endTime,
660
+ durationMs: this.durationMs,
661
+ error: this.error,
662
+ errorTrace: this.errorTrace,
663
+ attachments: this.attachments,
664
+ children: this.children.map((c) => c.toResult()),
665
+ parameters: this.parameters
666
+ };
667
+ }
668
+ };
669
+ function step(name, descriptionOrFn, fn) {
670
+ const description = typeof descriptionOrFn === "string" ? descriptionOrFn : void 0;
671
+ const callback = typeof descriptionOrFn === "function" ? descriptionOrFn : fn;
672
+ const s = new Step(name, description);
673
+ s.start();
674
+ try {
675
+ const result = callback();
676
+ if (result instanceof Promise) {
677
+ return result.then((value) => {
678
+ s.end();
679
+ return value;
680
+ }).catch((error) => {
681
+ s.end(error);
682
+ throw error;
683
+ });
684
+ }
685
+ s.end();
686
+ return result;
687
+ } catch (error) {
688
+ s.end(error);
689
+ throw error;
690
+ }
691
+ }
692
+
693
+ // src/core/severity.ts
694
+ var Severity = /* @__PURE__ */ ((Severity3) => {
695
+ Severity3["BLOCKER"] = "blocker";
696
+ Severity3["CRITICAL"] = "critical";
697
+ Severity3["NORMAL"] = "normal";
698
+ Severity3["MINOR"] = "minor";
699
+ Severity3["TRIVIAL"] = "trivial";
700
+ return Severity3;
701
+ })(Severity || {});
702
+ function parseSeverity(value) {
703
+ const normalized = value.toLowerCase();
704
+ switch (normalized) {
705
+ case "blocker":
706
+ return "blocker" /* BLOCKER */;
707
+ case "critical":
708
+ return "critical" /* CRITICAL */;
709
+ case "minor":
710
+ return "minor" /* MINOR */;
711
+ case "trivial":
712
+ return "trivial" /* TRIVIAL */;
713
+ default:
714
+ return "normal" /* NORMAL */;
715
+ }
716
+ }
717
+
718
+ // src/core/decorators.ts
719
+ var QAGENTIC_METADATA = /* @__PURE__ */ Symbol("qagentic_metadata");
720
+ function getMetadata(target) {
721
+ const obj = target;
722
+ if (!obj[QAGENTIC_METADATA]) {
723
+ obj[QAGENTIC_METADATA] = {
724
+ labels: {},
725
+ links: [],
726
+ attachments: []
727
+ };
728
+ }
729
+ return obj[QAGENTIC_METADATA];
730
+ }
731
+ function addLabel(name, value) {
732
+ return function(target) {
733
+ const metadata = getMetadata(target);
734
+ metadata.labels[name] = value;
735
+ return target;
736
+ };
737
+ }
738
+ function feature(name) {
739
+ return addLabel("feature", name);
740
+ }
741
+ function story(name) {
742
+ return addLabel("story", name);
743
+ }
744
+ function epic(name) {
745
+ return addLabel("epic", name);
746
+ }
747
+ function severity(level) {
748
+ const sev = typeof level === "string" ? parseSeverity(level) : level;
749
+ return addLabel("severity", sev);
750
+ }
751
+ function tag(...tags) {
752
+ return function(target) {
753
+ const metadata = getMetadata(target);
754
+ const existingTags = metadata.labels["tags"] || [];
755
+ metadata.labels["tags"] = [...existingTags, ...tags];
756
+ return target;
757
+ };
758
+ }
759
+ function label(name, value) {
760
+ return addLabel(name, value);
761
+ }
762
+ function attach(data, name, type = "text/plain", extension) {
763
+ const attachmentId = v4();
764
+ let content;
765
+ let size;
766
+ if (Buffer.isBuffer(data)) {
767
+ content = data.toString("base64");
768
+ size = data.length;
769
+ } else {
770
+ content = data;
771
+ size = data.length;
772
+ }
773
+ const attachment = {
774
+ id: attachmentId,
775
+ name,
776
+ type,
777
+ extension,
778
+ content,
779
+ size,
780
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
781
+ };
782
+ const currentStep = getCurrentStep();
783
+ if (currentStep) {
784
+ currentStep.attachments.push(attachment);
785
+ }
786
+ return attachmentId;
787
+ }
788
+ function attachScreenshot(data, name = "Screenshot") {
789
+ if (typeof data === "string" && fs2.existsSync(data)) {
790
+ const content = fs2.readFileSync(data);
791
+ return attach(content, name, "image/png", "png");
792
+ }
793
+ return attach(data, name, "image/png", "png");
794
+ }
795
+ function attachJson(data, name = "JSON Data") {
796
+ const jsonStr = JSON.stringify(data, null, 2);
797
+ return attach(jsonStr, name, "application/json", "json");
798
+ }
799
+ function attachText(text, name = "Text") {
800
+ return attach(text, name, "text/plain", "txt");
801
+ }
802
+
803
+ // src/playwright/index.ts
804
+ var QAgenticPlaywrightReporter = class {
805
+ constructor(options = {}) {
806
+ this.currentRun = null;
807
+ this.options = options;
808
+ configure({
809
+ projectName: options.projectName || "playwright-project",
810
+ environment: options.environment || "local",
811
+ apiUrl: options.apiUrl,
812
+ apiKey: options.apiKey,
813
+ outputDir: options.outputDir || "./qagentic-results"
814
+ });
815
+ this.reporter = QAgenticReporter.getInstance();
816
+ }
817
+ async onBegin(config, suite) {
818
+ this.currentRun = await this.reporter.startRun({
819
+ name: `playwright_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "")}`,
820
+ projectName: this.options.projectName || "playwright-project",
821
+ environment: this.options.environment || "local",
822
+ branch: process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_REF_NAME,
823
+ commitHash: process.env.GITHUB_SHA || process.env.CI_COMMIT_SHA
824
+ });
825
+ }
826
+ async onTestEnd(test, result) {
827
+ const testResult = {
828
+ id: v4(),
829
+ name: test.title,
830
+ fullName: test.titlePath().join(" > "),
831
+ description: test.annotations?.find((a) => a.type === "description")?.description,
832
+ status: this.mapStatus(result.status),
833
+ startTime: new Date(result.startTime),
834
+ endTime: new Date(result.startTime.getTime() + result.duration),
835
+ durationMs: result.duration,
836
+ labels: this.extractLabels(test),
837
+ links: [],
838
+ parameters: {},
839
+ steps: this.mapSteps(result.steps),
840
+ attachments: this.mapAttachments(result.attachments),
841
+ filePath: test.location?.file,
842
+ lineNumber: test.location?.line,
843
+ retryCount: result.retry,
844
+ isRetry: result.retry > 0,
845
+ isFlaky: result.status === "passed" && result.retry > 0
846
+ };
847
+ if (result.error) {
848
+ testResult.errorMessage = result.error.message;
849
+ testResult.stackTrace = result.error.stack;
850
+ testResult.errorType = result.error.name || "Error";
851
+ }
852
+ await this.reporter.reportTest(testResult);
853
+ }
854
+ async onEnd(result) {
855
+ await this.reporter.endRun();
856
+ }
857
+ mapStatus(status) {
858
+ switch (status) {
859
+ case "passed":
860
+ return "passed" /* PASSED */;
861
+ case "failed":
862
+ return "failed" /* FAILED */;
863
+ case "timedOut":
864
+ return "broken" /* BROKEN */;
865
+ case "skipped":
866
+ return "skipped" /* SKIPPED */;
867
+ case "interrupted":
868
+ return "broken" /* BROKEN */;
869
+ default:
870
+ return "unknown" /* UNKNOWN */;
871
+ }
872
+ }
873
+ extractLabels(test) {
874
+ const labels = {
875
+ suite: test.parent?.title
876
+ };
877
+ for (const annotation of test.annotations || []) {
878
+ if (annotation.type === "feature") labels.feature = annotation.description;
879
+ if (annotation.type === "story") labels.story = annotation.description;
880
+ if (annotation.type === "severity") labels.severity = annotation.description;
881
+ if (annotation.type === "tag") {
882
+ const tags = labels.tags || [];
883
+ tags.push(annotation.description || "");
884
+ labels.tags = tags;
885
+ }
886
+ }
887
+ return labels;
888
+ }
889
+ mapSteps(steps) {
890
+ return (steps || []).map((step2) => ({
891
+ id: v4(),
892
+ name: step2.title,
893
+ status: step2.error ? "failed" /* FAILED */ : "passed" /* PASSED */,
894
+ startTime: new Date(step2.startTime),
895
+ endTime: new Date(step2.startTime.getTime() + step2.duration),
896
+ durationMs: step2.duration,
897
+ error: step2.error?.message,
898
+ errorTrace: step2.error?.stack,
899
+ attachments: [],
900
+ children: this.mapSteps(step2.steps || []),
901
+ parameters: {}
902
+ }));
903
+ }
904
+ mapAttachments(attachments) {
905
+ return (attachments || []).map((att) => ({
906
+ id: v4(),
907
+ name: att.name,
908
+ type: att.contentType,
909
+ content: att.path || "",
910
+ size: 0,
911
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
912
+ }));
913
+ }
914
+ };
915
+ function qagenticReporter(options = {}) {
916
+ return ["@qagentic/reporter/playwright", options];
917
+ }
918
+ var playwright_default = QAgenticPlaywrightReporter;
919
+
920
+ export { QAgenticPlaywrightReporter, Severity, Status, Step, attach, attachJson, attachScreenshot, attachText, playwright_default as default, epic, feature, label, qagenticReporter, severity, step, story, tag };
921
+ //# sourceMappingURL=index.mjs.map
922
+ //# sourceMappingURL=index.mjs.map