fair-playwright 1.0.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/LICENSE +21 -0
- package/README.md +262 -0
- package/dist/index.cjs +1422 -0
- package/dist/index.d.cts +429 -0
- package/dist/index.d.ts +429 -0
- package/dist/index.js +1387 -0
- package/dist/mcp-cli.js +504 -0
- package/package.json +88 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1422 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
FairReporter: () => FairReporter,
|
|
34
|
+
MCPServer: () => MCPServer,
|
|
35
|
+
createMCPServer: () => createMCPServer,
|
|
36
|
+
default: () => FairReporter,
|
|
37
|
+
e2e: () => e2e
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
|
|
41
|
+
// src/reporter/StepTracker.ts
|
|
42
|
+
var StepTracker = class {
|
|
43
|
+
tests = /* @__PURE__ */ new Map();
|
|
44
|
+
steps = /* @__PURE__ */ new Map();
|
|
45
|
+
durationThreshold;
|
|
46
|
+
autoDetect;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.durationThreshold = options?.durationThreshold ?? 1e3;
|
|
49
|
+
this.autoDetect = options?.autoDetect ?? true;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Start tracking a test
|
|
53
|
+
*/
|
|
54
|
+
startTest(testCase, _result) {
|
|
55
|
+
const testId = this.getTestId(testCase);
|
|
56
|
+
const test2 = {
|
|
57
|
+
id: testId,
|
|
58
|
+
title: testCase.title,
|
|
59
|
+
file: testCase.location.file,
|
|
60
|
+
status: "running",
|
|
61
|
+
startTime: Date.now(),
|
|
62
|
+
steps: [],
|
|
63
|
+
attachments: [],
|
|
64
|
+
consoleErrors: []
|
|
65
|
+
};
|
|
66
|
+
this.tests.set(testId, test2);
|
|
67
|
+
return testId;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Start tracking a step
|
|
71
|
+
*/
|
|
72
|
+
startStep(testCase, _result, step, parentStepId) {
|
|
73
|
+
const testId = this.getTestId(testCase);
|
|
74
|
+
const stepId = this.getStepId(testCase, step);
|
|
75
|
+
const stepMetadata = {
|
|
76
|
+
id: stepId,
|
|
77
|
+
title: this.cleanStepTitle(step.title),
|
|
78
|
+
level: this.classifyStep(step, parentStepId),
|
|
79
|
+
status: "running",
|
|
80
|
+
startTime: Date.now(),
|
|
81
|
+
parentId: parentStepId,
|
|
82
|
+
childIds: []
|
|
83
|
+
};
|
|
84
|
+
this.steps.set(stepId, stepMetadata);
|
|
85
|
+
if (parentStepId) {
|
|
86
|
+
const parent = this.steps.get(parentStepId);
|
|
87
|
+
if (parent) {
|
|
88
|
+
parent.childIds.push(stepId);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const test2 = this.tests.get(testId);
|
|
92
|
+
if (test2) {
|
|
93
|
+
test2.steps.push(stepMetadata);
|
|
94
|
+
}
|
|
95
|
+
return stepId;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* End tracking a step
|
|
99
|
+
*/
|
|
100
|
+
endStep(_testCase, _result, step, stepId) {
|
|
101
|
+
const stepMetadata = this.steps.get(stepId);
|
|
102
|
+
if (!stepMetadata) return;
|
|
103
|
+
stepMetadata.endTime = Date.now();
|
|
104
|
+
stepMetadata.duration = stepMetadata.endTime - stepMetadata.startTime;
|
|
105
|
+
stepMetadata.status = step.error ? "failed" : "passed";
|
|
106
|
+
if (step.error) {
|
|
107
|
+
stepMetadata.error = {
|
|
108
|
+
message: step.error.message || "Unknown error",
|
|
109
|
+
stack: step.error.stack
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (this.autoDetect && stepMetadata.duration > this.durationThreshold) {
|
|
113
|
+
stepMetadata.level = "major";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* End tracking a test
|
|
118
|
+
*/
|
|
119
|
+
endTest(testCase, result) {
|
|
120
|
+
const testId = this.getTestId(testCase);
|
|
121
|
+
const test2 = this.tests.get(testId);
|
|
122
|
+
if (!test2) return;
|
|
123
|
+
test2.endTime = Date.now();
|
|
124
|
+
test2.duration = result.duration;
|
|
125
|
+
test2.status = this.mapTestStatus(result.status);
|
|
126
|
+
if (result.error) {
|
|
127
|
+
test2.error = {
|
|
128
|
+
message: result.error.message || "Unknown error",
|
|
129
|
+
stack: result.error.stack,
|
|
130
|
+
location: `${testCase.location.file}:${testCase.location.line}:${testCase.location.column}`
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
test2.attachments = result.attachments.map((att) => ({
|
|
134
|
+
name: att.name,
|
|
135
|
+
path: att.path,
|
|
136
|
+
contentType: att.contentType
|
|
137
|
+
}));
|
|
138
|
+
this.extractConsoleErrors(test2, result);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Extract console errors from test result output
|
|
142
|
+
*/
|
|
143
|
+
extractConsoleErrors(test2, result) {
|
|
144
|
+
if (!test2.consoleErrors) {
|
|
145
|
+
test2.consoleErrors = [];
|
|
146
|
+
}
|
|
147
|
+
if (!result.stdout || !result.stderr) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const output = result.stdout.join("\n") + "\n" + result.stderr.join("\n");
|
|
151
|
+
const errorPatterns = [
|
|
152
|
+
/console\.error:\s*(.+)/gi,
|
|
153
|
+
/\[error\]\s*(.+)/gi,
|
|
154
|
+
/ERROR:\s*(.+)/gi
|
|
155
|
+
];
|
|
156
|
+
errorPatterns.forEach((pattern) => {
|
|
157
|
+
const matches = output.matchAll(pattern);
|
|
158
|
+
for (const match of matches) {
|
|
159
|
+
if (match[1]) {
|
|
160
|
+
test2.consoleErrors.push({
|
|
161
|
+
type: "error",
|
|
162
|
+
message: match[1].trim(),
|
|
163
|
+
timestamp: Date.now()
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get test metadata
|
|
171
|
+
*/
|
|
172
|
+
getTest(testId) {
|
|
173
|
+
return this.tests.get(testId);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get all tests
|
|
177
|
+
*/
|
|
178
|
+
getAllTests() {
|
|
179
|
+
return Array.from(this.tests.values());
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get step metadata
|
|
183
|
+
*/
|
|
184
|
+
getStep(stepId) {
|
|
185
|
+
return this.steps.get(stepId);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Clear all tracking data
|
|
189
|
+
*/
|
|
190
|
+
clear() {
|
|
191
|
+
this.tests.clear();
|
|
192
|
+
this.steps.clear();
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Generate unique test ID
|
|
196
|
+
*/
|
|
197
|
+
getTestId(testCase) {
|
|
198
|
+
return `${testCase.location.file}::${testCase.title}`;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Generate unique step ID
|
|
202
|
+
*/
|
|
203
|
+
getStepId(testCase, step) {
|
|
204
|
+
return `${this.getTestId(testCase)}::${step.title}::${Date.now()}`;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Classify step as MAJOR or MINOR
|
|
208
|
+
*/
|
|
209
|
+
classifyStep(step, parentStepId) {
|
|
210
|
+
const prefixMatch = step.title.match(/^\[(MAJOR|MINOR)\]\s*/);
|
|
211
|
+
if (prefixMatch) {
|
|
212
|
+
return prefixMatch[1].toLowerCase();
|
|
213
|
+
}
|
|
214
|
+
if (parentStepId) {
|
|
215
|
+
return "minor";
|
|
216
|
+
}
|
|
217
|
+
const majorKeywords = ["login", "checkout", "payment", "register", "setup", "flow"];
|
|
218
|
+
const titleLower = step.title.toLowerCase();
|
|
219
|
+
if (majorKeywords.some((keyword) => titleLower.includes(keyword))) {
|
|
220
|
+
return "major";
|
|
221
|
+
}
|
|
222
|
+
return "minor";
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Clean step title by removing [MAJOR]/[MINOR] prefix
|
|
226
|
+
*/
|
|
227
|
+
cleanStepTitle(title) {
|
|
228
|
+
return title.replace(/^\[(MAJOR|MINOR)\]\s*/, "");
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Map Playwright test status to our status
|
|
232
|
+
*/
|
|
233
|
+
mapTestStatus(status) {
|
|
234
|
+
switch (status) {
|
|
235
|
+
case "passed":
|
|
236
|
+
return "passed";
|
|
237
|
+
case "failed":
|
|
238
|
+
return "failed";
|
|
239
|
+
case "skipped":
|
|
240
|
+
return "skipped";
|
|
241
|
+
case "timedOut":
|
|
242
|
+
return "failed";
|
|
243
|
+
default:
|
|
244
|
+
return "failed";
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// src/formatters/ConsoleFormatter.ts
|
|
250
|
+
var import_picocolors = __toESM(require("picocolors"), 1);
|
|
251
|
+
var import_log_update = __toESM(require("log-update"), 1);
|
|
252
|
+
var ConsoleFormatter = class {
|
|
253
|
+
config;
|
|
254
|
+
totalTests = 0;
|
|
255
|
+
completedTests = 0;
|
|
256
|
+
passedTests = 0;
|
|
257
|
+
failedTests = 0;
|
|
258
|
+
skippedTests = 0;
|
|
259
|
+
runningSteps = /* @__PURE__ */ new Map();
|
|
260
|
+
isCI;
|
|
261
|
+
useProgressiveMode;
|
|
262
|
+
updateTimer;
|
|
263
|
+
constructor(config) {
|
|
264
|
+
this.config = config;
|
|
265
|
+
this.isCI = this.detectCI();
|
|
266
|
+
this.useProgressiveMode = config.mode === "progressive" && !this.isCI && process.stdout.isTTY === true;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Detect if running in CI environment
|
|
270
|
+
*/
|
|
271
|
+
detectCI() {
|
|
272
|
+
return !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.CIRCLECI || process.env.TRAVIS || process.env.JENKINS_URL);
|
|
273
|
+
}
|
|
274
|
+
onBegin(totalTests) {
|
|
275
|
+
this.totalTests = totalTests;
|
|
276
|
+
const header = import_picocolors.default.bold(import_picocolors.default.blue("\u{1F3AD} Fair Playwright Reporter"));
|
|
277
|
+
const subheader = import_picocolors.default.dim(`Running ${totalTests} test(s)...`);
|
|
278
|
+
if (this.useProgressiveMode) {
|
|
279
|
+
console.log(`
|
|
280
|
+
${header}
|
|
281
|
+
${subheader}
|
|
282
|
+
`);
|
|
283
|
+
} else {
|
|
284
|
+
console.log(`
|
|
285
|
+
${header}
|
|
286
|
+
${subheader}
|
|
287
|
+
`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
onTestBegin(test2) {
|
|
291
|
+
if (!this.useProgressiveMode && this.config.mode !== "minimal") {
|
|
292
|
+
console.log(import_picocolors.default.dim(`\u23F3 ${test2.title}`));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
onStepBegin(step) {
|
|
296
|
+
this.runningSteps.set(step.id, step);
|
|
297
|
+
if (this.useProgressiveMode) {
|
|
298
|
+
this.scheduleUpdate();
|
|
299
|
+
} else if (this.config.mode === "full") {
|
|
300
|
+
const indent = step.parentId ? " " : " ";
|
|
301
|
+
const icon = step.level === "major" ? "\u25B6" : "\u25B8";
|
|
302
|
+
console.log(import_picocolors.default.dim(`${indent}${icon} ${step.title}...`));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
onStepEnd(step) {
|
|
306
|
+
this.runningSteps.delete(step.id);
|
|
307
|
+
if (this.useProgressiveMode) {
|
|
308
|
+
this.scheduleUpdate();
|
|
309
|
+
} else if (this.config.mode === "full") {
|
|
310
|
+
const indent = step.parentId ? " " : " ";
|
|
311
|
+
const icon = step.status === "passed" ? import_picocolors.default.green("\u2713") : import_picocolors.default.red("\u2717");
|
|
312
|
+
const duration = step.duration ? import_picocolors.default.dim(` (${step.duration}ms)`) : "";
|
|
313
|
+
const levelBadge = step.level === "major" ? import_picocolors.default.blue("[MAJOR]") : import_picocolors.default.dim("[minor]");
|
|
314
|
+
console.log(`${indent}${icon} ${levelBadge} ${step.title}${duration}`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
onTestEnd(test2) {
|
|
318
|
+
this.completedTests++;
|
|
319
|
+
if (test2.status === "passed") {
|
|
320
|
+
this.passedTests++;
|
|
321
|
+
} else if (test2.status === "failed") {
|
|
322
|
+
this.failedTests++;
|
|
323
|
+
} else if (test2.status === "skipped") {
|
|
324
|
+
this.skippedTests++;
|
|
325
|
+
}
|
|
326
|
+
if (this.useProgressiveMode) {
|
|
327
|
+
if (test2.status === "failed") {
|
|
328
|
+
this.clearUpdate();
|
|
329
|
+
this.printFailedTest(test2);
|
|
330
|
+
}
|
|
331
|
+
this.scheduleUpdate();
|
|
332
|
+
} else {
|
|
333
|
+
if (test2.status === "passed") {
|
|
334
|
+
if (this.config.compression.passedTests !== "hide") {
|
|
335
|
+
const icon = import_picocolors.default.green("\u2713");
|
|
336
|
+
const duration = import_picocolors.default.dim(` (${test2.duration}ms)`);
|
|
337
|
+
console.log(`${icon} ${import_picocolors.default.dim(test2.title)}${duration}`);
|
|
338
|
+
}
|
|
339
|
+
} else if (test2.status === "failed") {
|
|
340
|
+
this.printFailedTest(test2);
|
|
341
|
+
} else if (test2.status === "skipped") {
|
|
342
|
+
console.log(import_picocolors.default.yellow(`\u2298 ${test2.title} (skipped)`));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
onEnd(allTests, _result) {
|
|
347
|
+
if (this.useProgressiveMode) {
|
|
348
|
+
this.clearUpdate();
|
|
349
|
+
if (this.updateTimer) {
|
|
350
|
+
clearTimeout(this.updateTimer);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
console.log("");
|
|
354
|
+
console.log(import_picocolors.default.bold("\u2500".repeat(60)));
|
|
355
|
+
console.log("");
|
|
356
|
+
const passed = allTests.filter((t) => t.status === "passed").length;
|
|
357
|
+
const failed = allTests.filter((t) => t.status === "failed").length;
|
|
358
|
+
const skipped = allTests.filter((t) => t.status === "skipped").length;
|
|
359
|
+
const totalDuration = allTests.reduce((sum, t) => sum + (t.duration || 0), 0);
|
|
360
|
+
if (failed > 0) {
|
|
361
|
+
console.log(import_picocolors.default.red(import_picocolors.default.bold(`\u2717 ${failed} failed`)));
|
|
362
|
+
}
|
|
363
|
+
if (passed > 0) {
|
|
364
|
+
console.log(import_picocolors.default.green(`\u2713 ${passed} passed`));
|
|
365
|
+
}
|
|
366
|
+
if (skipped > 0) {
|
|
367
|
+
console.log(import_picocolors.default.yellow(`\u2298 ${skipped} skipped`));
|
|
368
|
+
}
|
|
369
|
+
console.log(import_picocolors.default.dim(`
|
|
370
|
+
Total: ${allTests.length} test(s)`));
|
|
371
|
+
console.log(import_picocolors.default.dim(`Duration: ${(totalDuration / 1e3).toFixed(2)}s`));
|
|
372
|
+
if (this.config.output.ai) {
|
|
373
|
+
const aiPath = typeof this.config.output.ai === "string" ? this.config.output.ai : "./test-results/ai-summary.md";
|
|
374
|
+
console.log(import_picocolors.default.dim(`
|
|
375
|
+
\u{1F4DD} AI Summary: ${aiPath}`));
|
|
376
|
+
}
|
|
377
|
+
console.log("");
|
|
378
|
+
}
|
|
379
|
+
onError(error) {
|
|
380
|
+
if (this.useProgressiveMode) {
|
|
381
|
+
this.clearUpdate();
|
|
382
|
+
}
|
|
383
|
+
console.error(import_picocolors.default.red(`
|
|
384
|
+
\u274C Reporter Error: ${error.message}`));
|
|
385
|
+
if (error.stack) {
|
|
386
|
+
console.error(import_picocolors.default.dim(error.stack));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Schedule a progressive update
|
|
391
|
+
*/
|
|
392
|
+
scheduleUpdate() {
|
|
393
|
+
if (!this.useProgressiveMode) return;
|
|
394
|
+
if (this.updateTimer) {
|
|
395
|
+
clearTimeout(this.updateTimer);
|
|
396
|
+
}
|
|
397
|
+
this.updateTimer = setTimeout(() => {
|
|
398
|
+
this.render();
|
|
399
|
+
}, this.config.progressive.updateInterval);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Render progressive output
|
|
403
|
+
*/
|
|
404
|
+
render() {
|
|
405
|
+
if (!this.useProgressiveMode) return;
|
|
406
|
+
const lines = [];
|
|
407
|
+
const progress = this.totalTests > 0 ? Math.floor(this.completedTests / this.totalTests * 100) : 0;
|
|
408
|
+
lines.push(
|
|
409
|
+
import_picocolors.default.dim(`Progress: ${this.completedTests}/${this.totalTests} tests `) + import_picocolors.default.green(`(${progress}%)`)
|
|
410
|
+
);
|
|
411
|
+
const statusParts = [];
|
|
412
|
+
if (this.passedTests > 0) statusParts.push(import_picocolors.default.green(`\u2713 ${this.passedTests}`));
|
|
413
|
+
if (this.failedTests > 0) statusParts.push(import_picocolors.default.red(`\u2717 ${this.failedTests}`));
|
|
414
|
+
if (this.skippedTests > 0) statusParts.push(import_picocolors.default.yellow(`\u2298 ${this.skippedTests}`));
|
|
415
|
+
if (statusParts.length > 0) {
|
|
416
|
+
lines.push(statusParts.join(" "));
|
|
417
|
+
}
|
|
418
|
+
if (this.runningSteps.size > 0) {
|
|
419
|
+
lines.push("");
|
|
420
|
+
lines.push(import_picocolors.default.dim("Running:"));
|
|
421
|
+
const runningStepsArray = Array.from(this.runningSteps.values());
|
|
422
|
+
runningStepsArray.slice(0, 5).forEach((step) => {
|
|
423
|
+
const indent = step.parentId ? " " : " ";
|
|
424
|
+
const icon = step.level === "major" ? "\u25B6" : "\u25B8";
|
|
425
|
+
const levelBadge = step.level === "major" ? import_picocolors.default.blue("[MAJOR]") : import_picocolors.default.dim("[minor]");
|
|
426
|
+
const elapsed = Date.now() - step.startTime;
|
|
427
|
+
lines.push(`${indent}${icon} ${levelBadge} ${step.title} ${import_picocolors.default.dim(`(${elapsed}ms)`)}`);
|
|
428
|
+
});
|
|
429
|
+
if (runningStepsArray.length > 5) {
|
|
430
|
+
lines.push(import_picocolors.default.dim(` ... and ${runningStepsArray.length - 5} more`));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
(0, import_log_update.default)(lines.join("\n"));
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Clear progressive update
|
|
437
|
+
*/
|
|
438
|
+
clearUpdate() {
|
|
439
|
+
if (this.useProgressiveMode) {
|
|
440
|
+
import_log_update.default.clear();
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Print failed test details with progressive MAJOR step display
|
|
445
|
+
*/
|
|
446
|
+
printFailedTest(test2) {
|
|
447
|
+
console.log(import_picocolors.default.red(`\u2717 ${test2.title}`));
|
|
448
|
+
console.log("");
|
|
449
|
+
const majorSteps = test2.steps.filter((s) => s.level === "major" && !s.parentId);
|
|
450
|
+
if (majorSteps.length > 0) {
|
|
451
|
+
majorSteps.forEach((majorStep, index) => {
|
|
452
|
+
const stepNumber = index + 1;
|
|
453
|
+
if (majorStep.status === "passed") {
|
|
454
|
+
const duration = majorStep.duration ? import_picocolors.default.dim(` (${majorStep.duration}ms)`) : "";
|
|
455
|
+
const successMsg = majorStep.successMessage ? import_picocolors.default.dim(` - ${majorStep.successMessage}`) : "";
|
|
456
|
+
console.log(import_picocolors.default.green(` ${stepNumber}. \u2713 [MAJOR] ${majorStep.title}${successMsg}${duration}`));
|
|
457
|
+
} else if (majorStep.status === "failed") {
|
|
458
|
+
console.log(import_picocolors.default.red(` ${stepNumber}. \u2717 [MAJOR] ${majorStep.title}`));
|
|
459
|
+
const minorSteps = test2.steps.filter((s) => s.parentId === majorStep.id);
|
|
460
|
+
if (minorSteps.length > 0) {
|
|
461
|
+
minorSteps.forEach((minorStep, minorIndex) => {
|
|
462
|
+
const minorNumber = minorIndex + 1;
|
|
463
|
+
const duration = minorStep.duration ? import_picocolors.default.dim(` (${minorStep.duration}ms)`) : "";
|
|
464
|
+
if (minorStep.status === "passed") {
|
|
465
|
+
console.log(import_picocolors.default.green(` ${minorNumber}. \u2713 [minor] ${minorStep.title}${duration}`));
|
|
466
|
+
} else if (minorStep.status === "failed") {
|
|
467
|
+
console.log(import_picocolors.default.red(` ${minorNumber}. \u2717 [minor] ${minorStep.title}${duration}`));
|
|
468
|
+
if (minorStep.error) {
|
|
469
|
+
console.log(import_picocolors.default.dim(` ${minorStep.error.message}`));
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
console.log(import_picocolors.default.dim(` ${minorNumber}. \u2298 [minor] ${minorStep.title}`));
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
if (majorStep.error) {
|
|
477
|
+
console.log("");
|
|
478
|
+
console.log(import_picocolors.default.red(` Error: ${majorStep.error.message}`));
|
|
479
|
+
}
|
|
480
|
+
} else if (majorStep.status === "skipped") {
|
|
481
|
+
console.log(import_picocolors.default.yellow(` ${stepNumber}. \u2298 [MAJOR] ${majorStep.title} (skipped)`));
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
if (test2.error) {
|
|
486
|
+
console.log("");
|
|
487
|
+
console.log(import_picocolors.default.red(` Error Message: ${test2.error.message}`));
|
|
488
|
+
if (test2.error.stack) {
|
|
489
|
+
console.log("");
|
|
490
|
+
console.log(import_picocolors.default.dim(" Stack Trace:"));
|
|
491
|
+
const stackLines = test2.error.stack.split("\n").slice(0, 5);
|
|
492
|
+
stackLines.forEach((line) => {
|
|
493
|
+
console.log(import_picocolors.default.dim(` ${line}`));
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
if (test2.error.location) {
|
|
497
|
+
console.log(import_picocolors.default.dim(` Location: ${test2.error.location}`));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (test2.consoleErrors && test2.consoleErrors.length > 0) {
|
|
501
|
+
console.log("");
|
|
502
|
+
console.log(import_picocolors.default.yellow(` Browser Console Errors (${test2.consoleErrors.length}):`));
|
|
503
|
+
test2.consoleErrors.forEach((consoleError, index) => {
|
|
504
|
+
console.log(import_picocolors.default.yellow(` ${index + 1}. [${consoleError.type}] ${consoleError.message}`));
|
|
505
|
+
if (consoleError.location) {
|
|
506
|
+
console.log(import_picocolors.default.dim(` at ${consoleError.location}`));
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
console.log("");
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
// src/formatters/AIFormatter.ts
|
|
515
|
+
var import_promises = require("fs/promises");
|
|
516
|
+
var import_path = require("path");
|
|
517
|
+
var AIFormatter = class {
|
|
518
|
+
config;
|
|
519
|
+
outputPath;
|
|
520
|
+
constructor(config, outputPath) {
|
|
521
|
+
this.config = config;
|
|
522
|
+
this.outputPath = outputPath;
|
|
523
|
+
}
|
|
524
|
+
async write(allTests, _result) {
|
|
525
|
+
const markdown = this.generateMarkdown(allTests, _result);
|
|
526
|
+
try {
|
|
527
|
+
await (0, import_promises.mkdir)((0, import_path.dirname)(this.outputPath), { recursive: true });
|
|
528
|
+
await (0, import_promises.writeFile)(this.outputPath, markdown, "utf-8");
|
|
529
|
+
} catch (error) {
|
|
530
|
+
console.error(`Failed to write AI summary: ${error}`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
generateMarkdown(allTests, _result) {
|
|
534
|
+
const passed = allTests.filter((t) => t.status === "passed").length;
|
|
535
|
+
const failed = allTests.filter((t) => t.status === "failed").length;
|
|
536
|
+
const skipped = allTests.filter((t) => t.status === "skipped").length;
|
|
537
|
+
const totalDuration = allTests.reduce((sum, t) => sum + (t.duration || 0), 0);
|
|
538
|
+
const overallStatus = failed > 0 ? "\u274C FAILED" : "\u2705 PASSED";
|
|
539
|
+
let md = `# Test Results
|
|
540
|
+
|
|
541
|
+
`;
|
|
542
|
+
md += `**Status**: ${overallStatus} (${passed}/${allTests.length} tests passed)
|
|
543
|
+
`;
|
|
544
|
+
md += `**Duration**: ${(totalDuration / 1e3).toFixed(2)}s
|
|
545
|
+
`;
|
|
546
|
+
md += `**Date**: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
547
|
+
|
|
548
|
+
`;
|
|
549
|
+
md += `## Summary
|
|
550
|
+
|
|
551
|
+
`;
|
|
552
|
+
md += `- \u2705 Passed: ${passed}
|
|
553
|
+
`;
|
|
554
|
+
md += `- \u274C Failed: ${failed}
|
|
555
|
+
`;
|
|
556
|
+
md += `- \u2298 Skipped: ${skipped}
|
|
557
|
+
`;
|
|
558
|
+
md += `- \u{1F4CA} Total: ${allTests.length}
|
|
559
|
+
|
|
560
|
+
`;
|
|
561
|
+
const failedTests = allTests.filter((t) => t.status === "failed");
|
|
562
|
+
if (failedTests.length > 0) {
|
|
563
|
+
md += `## \u274C Failed Tests
|
|
564
|
+
|
|
565
|
+
`;
|
|
566
|
+
failedTests.forEach((test2) => {
|
|
567
|
+
md += `### ${test2.title}
|
|
568
|
+
|
|
569
|
+
`;
|
|
570
|
+
md += `**File**: \`${test2.file}\`
|
|
571
|
+
`;
|
|
572
|
+
md += `**Duration**: ${test2.duration}ms
|
|
573
|
+
|
|
574
|
+
`;
|
|
575
|
+
if (test2.error) {
|
|
576
|
+
md += `**Error**: ${test2.error.message}
|
|
577
|
+
|
|
578
|
+
`;
|
|
579
|
+
if (test2.error.location) {
|
|
580
|
+
md += `**Location**: \`${test2.error.location}\`
|
|
581
|
+
|
|
582
|
+
`;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (test2.steps.length > 0) {
|
|
586
|
+
md += `**Steps Executed**:
|
|
587
|
+
|
|
588
|
+
`;
|
|
589
|
+
test2.steps.forEach((step, index) => {
|
|
590
|
+
const icon = step.status === "passed" ? "\u2705" : step.status === "failed" ? "\u274C" : "\u2298";
|
|
591
|
+
const level = step.level === "major" ? "**MAJOR**" : "minor";
|
|
592
|
+
const duration = step.duration ? ` (${step.duration}ms)` : "";
|
|
593
|
+
md += `${index + 1}. ${icon} [${level}] ${step.title}${duration}
|
|
594
|
+
`;
|
|
595
|
+
if (step.status === "failed" && step.error) {
|
|
596
|
+
md += ` - Error: ${step.error.message}
|
|
597
|
+
`;
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
md += `
|
|
601
|
+
`;
|
|
602
|
+
}
|
|
603
|
+
if (test2.consoleErrors && test2.consoleErrors.length > 0) {
|
|
604
|
+
md += `**Browser Console Errors** (${test2.consoleErrors.length}):
|
|
605
|
+
|
|
606
|
+
`;
|
|
607
|
+
test2.consoleErrors.forEach((consoleError, index) => {
|
|
608
|
+
md += `${index + 1}. **[${consoleError.type}]** ${consoleError.message}
|
|
609
|
+
`;
|
|
610
|
+
if (consoleError.location) {
|
|
611
|
+
md += ` - Location: \`${consoleError.location}\`
|
|
612
|
+
`;
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
md += `
|
|
616
|
+
`;
|
|
617
|
+
}
|
|
618
|
+
if (test2.attachments.length > 0) {
|
|
619
|
+
md += `**Artifacts**:
|
|
620
|
+
|
|
621
|
+
`;
|
|
622
|
+
test2.attachments.forEach((att) => {
|
|
623
|
+
if (att.path) {
|
|
624
|
+
md += `- ${att.name}: \`${att.path}\`
|
|
625
|
+
`;
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
md += `
|
|
629
|
+
`;
|
|
630
|
+
}
|
|
631
|
+
md += `---
|
|
632
|
+
|
|
633
|
+
`;
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
if (this.config.compression.passedTests !== "hide") {
|
|
637
|
+
const passedTests = allTests.filter((t) => t.status === "passed");
|
|
638
|
+
if (passedTests.length > 0) {
|
|
639
|
+
md += `## \u2705 Passed Tests
|
|
640
|
+
|
|
641
|
+
`;
|
|
642
|
+
if (this.config.compression.passedTests === "summary") {
|
|
643
|
+
md += `All ${passedTests.length} test(s) passed:
|
|
644
|
+
|
|
645
|
+
`;
|
|
646
|
+
passedTests.forEach((test2) => {
|
|
647
|
+
md += `- ${test2.title} (${test2.duration}ms)
|
|
648
|
+
`;
|
|
649
|
+
});
|
|
650
|
+
md += `
|
|
651
|
+
`;
|
|
652
|
+
} else {
|
|
653
|
+
passedTests.forEach((test2) => {
|
|
654
|
+
md += `### ${test2.title}
|
|
655
|
+
|
|
656
|
+
`;
|
|
657
|
+
md += `**Duration**: ${test2.duration}ms
|
|
658
|
+
|
|
659
|
+
`;
|
|
660
|
+
if (test2.steps.length > 0) {
|
|
661
|
+
md += `**Steps**:
|
|
662
|
+
|
|
663
|
+
`;
|
|
664
|
+
test2.steps.forEach((step, index) => {
|
|
665
|
+
md += `${index + 1}. \u2705 ${step.title} (${step.duration}ms)
|
|
666
|
+
`;
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
md += `
|
|
670
|
+
`;
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const skippedTests = allTests.filter((t) => t.status === "skipped");
|
|
676
|
+
if (skippedTests.length > 0) {
|
|
677
|
+
md += `## \u2298 Skipped Tests
|
|
678
|
+
|
|
679
|
+
`;
|
|
680
|
+
skippedTests.forEach((test2) => {
|
|
681
|
+
md += `- ${test2.title}
|
|
682
|
+
`;
|
|
683
|
+
});
|
|
684
|
+
md += `
|
|
685
|
+
`;
|
|
686
|
+
}
|
|
687
|
+
md += `---
|
|
688
|
+
|
|
689
|
+
`;
|
|
690
|
+
md += `*Generated by [fair-playwright](https://github.com/baranaytas/fair-playwright)*
|
|
691
|
+
`;
|
|
692
|
+
return md;
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
// src/formatters/JSONFormatter.ts
|
|
697
|
+
var import_promises2 = require("fs/promises");
|
|
698
|
+
var import_path2 = require("path");
|
|
699
|
+
var JSONFormatter = class {
|
|
700
|
+
outputPath;
|
|
701
|
+
constructor(_config, outputPath) {
|
|
702
|
+
this.outputPath = outputPath;
|
|
703
|
+
}
|
|
704
|
+
async write(allTests, _result) {
|
|
705
|
+
const passed = allTests.filter((t) => t.status === "passed").length;
|
|
706
|
+
const failed = allTests.filter((t) => t.status === "failed").length;
|
|
707
|
+
const skipped = allTests.filter((t) => t.status === "skipped").length;
|
|
708
|
+
const totalDuration = allTests.reduce((sum, t) => sum + (t.duration || 0), 0);
|
|
709
|
+
const output = {
|
|
710
|
+
status: failed > 0 ? "failed" : "passed",
|
|
711
|
+
summary: {
|
|
712
|
+
total: allTests.length,
|
|
713
|
+
passed,
|
|
714
|
+
failed,
|
|
715
|
+
skipped,
|
|
716
|
+
duration: totalDuration
|
|
717
|
+
},
|
|
718
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
719
|
+
tests: allTests.map((test2) => ({
|
|
720
|
+
id: test2.id,
|
|
721
|
+
title: test2.title,
|
|
722
|
+
file: test2.file,
|
|
723
|
+
status: test2.status,
|
|
724
|
+
duration: test2.duration,
|
|
725
|
+
startTime: test2.startTime,
|
|
726
|
+
endTime: test2.endTime,
|
|
727
|
+
error: test2.error ? {
|
|
728
|
+
message: test2.error.message,
|
|
729
|
+
stack: test2.error.stack,
|
|
730
|
+
location: test2.error.location
|
|
731
|
+
} : void 0,
|
|
732
|
+
steps: test2.steps.map((step) => ({
|
|
733
|
+
id: step.id,
|
|
734
|
+
title: step.title,
|
|
735
|
+
level: step.level,
|
|
736
|
+
status: step.status,
|
|
737
|
+
duration: step.duration,
|
|
738
|
+
error: step.error ? {
|
|
739
|
+
message: step.error.message,
|
|
740
|
+
stack: step.error.stack
|
|
741
|
+
} : void 0
|
|
742
|
+
})),
|
|
743
|
+
attachments: test2.attachments
|
|
744
|
+
}))
|
|
745
|
+
};
|
|
746
|
+
try {
|
|
747
|
+
await (0, import_promises2.mkdir)((0, import_path2.dirname)(this.outputPath), { recursive: true });
|
|
748
|
+
await (0, import_promises2.writeFile)(this.outputPath, JSON.stringify(output, null, 2), "utf-8");
|
|
749
|
+
} catch (error) {
|
|
750
|
+
console.error(`Failed to write JSON output: ${error}`);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
// src/reporter/FairReporter.ts
|
|
756
|
+
var FairReporter = class {
|
|
757
|
+
config;
|
|
758
|
+
stepTracker;
|
|
759
|
+
consoleFormatter;
|
|
760
|
+
aiFormatter;
|
|
761
|
+
jsonFormatter;
|
|
762
|
+
stepIdMap = /* @__PURE__ */ new Map();
|
|
763
|
+
constructor(config) {
|
|
764
|
+
this.config = {
|
|
765
|
+
mode: config?.mode ?? "progressive",
|
|
766
|
+
aiOptimized: config?.aiOptimized ?? true,
|
|
767
|
+
output: {
|
|
768
|
+
console: config?.output?.console ?? true,
|
|
769
|
+
ai: config?.output?.ai ?? false,
|
|
770
|
+
json: config?.output?.json ?? false
|
|
771
|
+
},
|
|
772
|
+
stepClassification: {
|
|
773
|
+
durationThreshold: config?.stepClassification?.durationThreshold ?? 1e3,
|
|
774
|
+
autoDetect: config?.stepClassification?.autoDetect ?? true
|
|
775
|
+
},
|
|
776
|
+
progressive: {
|
|
777
|
+
clearCompleted: config?.progressive?.clearCompleted ?? true,
|
|
778
|
+
updateInterval: config?.progressive?.updateInterval ?? 100
|
|
779
|
+
},
|
|
780
|
+
compression: {
|
|
781
|
+
passedTests: config?.compression?.passedTests ?? "summary",
|
|
782
|
+
failureContext: {
|
|
783
|
+
steps: config?.compression?.failureContext?.steps ?? 3,
|
|
784
|
+
screenshot: config?.compression?.failureContext?.screenshot ?? true,
|
|
785
|
+
trace: config?.compression?.failureContext?.trace ?? true,
|
|
786
|
+
logs: config?.compression?.failureContext?.logs ?? true
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
this.stepTracker = new StepTracker({
|
|
791
|
+
durationThreshold: this.config.stepClassification.durationThreshold,
|
|
792
|
+
autoDetect: this.config.stepClassification.autoDetect
|
|
793
|
+
});
|
|
794
|
+
if (this.config.output.console) {
|
|
795
|
+
this.consoleFormatter = new ConsoleFormatter(this.config);
|
|
796
|
+
}
|
|
797
|
+
if (this.config.output.ai) {
|
|
798
|
+
const outputPath = typeof this.config.output.ai === "string" ? this.config.output.ai : "./test-results/ai-summary.md";
|
|
799
|
+
this.aiFormatter = new AIFormatter(this.config, outputPath);
|
|
800
|
+
}
|
|
801
|
+
if (this.config.output.json) {
|
|
802
|
+
const outputPath = typeof this.config.output.json === "string" ? this.config.output.json : "./test-results/results.json";
|
|
803
|
+
this.jsonFormatter = new JSONFormatter(this.config, outputPath);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Called once before running tests
|
|
808
|
+
*/
|
|
809
|
+
onBegin(_config, suite) {
|
|
810
|
+
const totalTests = suite.allTests().length;
|
|
811
|
+
if (this.consoleFormatter) {
|
|
812
|
+
this.consoleFormatter.onBegin(totalTests);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Called when a test begins
|
|
817
|
+
*/
|
|
818
|
+
onTestBegin(test2, result) {
|
|
819
|
+
this.stepTracker.startTest(test2, result);
|
|
820
|
+
if (this.consoleFormatter) {
|
|
821
|
+
this.consoleFormatter.onTestBegin(test2);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Called when a test step begins
|
|
826
|
+
*/
|
|
827
|
+
onStepBegin(test2, result, step) {
|
|
828
|
+
let parentStepId;
|
|
829
|
+
if (step.parent) {
|
|
830
|
+
parentStepId = this.stepIdMap.get(step.parent);
|
|
831
|
+
}
|
|
832
|
+
const stepId = this.stepTracker.startStep(test2, result, step, parentStepId);
|
|
833
|
+
this.stepIdMap.set(step, stepId);
|
|
834
|
+
if (this.consoleFormatter) {
|
|
835
|
+
const stepMetadata = this.stepTracker.getStep(stepId);
|
|
836
|
+
if (stepMetadata) {
|
|
837
|
+
this.consoleFormatter.onStepBegin(stepMetadata);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Called when a test step ends
|
|
843
|
+
*/
|
|
844
|
+
onStepEnd(test2, result, step) {
|
|
845
|
+
const stepId = this.stepIdMap.get(step);
|
|
846
|
+
if (!stepId) return;
|
|
847
|
+
this.stepTracker.endStep(test2, result, step, stepId);
|
|
848
|
+
if (this.consoleFormatter) {
|
|
849
|
+
const stepMetadata = this.stepTracker.getStep(stepId);
|
|
850
|
+
if (stepMetadata) {
|
|
851
|
+
this.consoleFormatter.onStepEnd(stepMetadata);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Called when a test ends
|
|
857
|
+
*/
|
|
858
|
+
onTestEnd(test2, result) {
|
|
859
|
+
this.stepTracker.endTest(test2, result);
|
|
860
|
+
if (this.consoleFormatter) {
|
|
861
|
+
const testId = `${test2.location.file}::${test2.title}`;
|
|
862
|
+
const testMetadata = this.stepTracker.getTest(testId);
|
|
863
|
+
if (testMetadata) {
|
|
864
|
+
this.consoleFormatter.onTestEnd(testMetadata);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Called once after all tests have finished
|
|
870
|
+
*/
|
|
871
|
+
async onEnd(result) {
|
|
872
|
+
const allTests = this.stepTracker.getAllTests();
|
|
873
|
+
if (this.consoleFormatter) {
|
|
874
|
+
this.consoleFormatter.onEnd(allTests, result);
|
|
875
|
+
}
|
|
876
|
+
if (this.aiFormatter) {
|
|
877
|
+
await this.aiFormatter.write(allTests, result);
|
|
878
|
+
}
|
|
879
|
+
if (this.jsonFormatter) {
|
|
880
|
+
await this.jsonFormatter.write(allTests, result);
|
|
881
|
+
}
|
|
882
|
+
this.stepIdMap.clear();
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Optional: Called on error
|
|
886
|
+
*/
|
|
887
|
+
onError(error) {
|
|
888
|
+
if (this.consoleFormatter) {
|
|
889
|
+
this.consoleFormatter.onError(error);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
// src/e2e.ts
|
|
895
|
+
var import_test = require("@playwright/test");
|
|
896
|
+
var E2EHelperImpl = class {
|
|
897
|
+
/**
|
|
898
|
+
* Execute a major step
|
|
899
|
+
* Supports both inline and declarative modes
|
|
900
|
+
*/
|
|
901
|
+
async major(title, actionOrOptions, options) {
|
|
902
|
+
if (typeof actionOrOptions === "object" && "steps" in actionOrOptions) {
|
|
903
|
+
return this.majorDeclarative(title, actionOrOptions);
|
|
904
|
+
}
|
|
905
|
+
if (typeof actionOrOptions === "function") {
|
|
906
|
+
return this.majorInline(title, actionOrOptions, options);
|
|
907
|
+
}
|
|
908
|
+
throw new Error("Invalid arguments for e2e.major()");
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Execute a minor step (inline mode only)
|
|
912
|
+
*/
|
|
913
|
+
async minor(title, action, options) {
|
|
914
|
+
const stepTitle = this.formatStepTitle(title, "minor", options);
|
|
915
|
+
return import_test.test.step(stepTitle, async () => {
|
|
916
|
+
try {
|
|
917
|
+
await action();
|
|
918
|
+
} catch (error) {
|
|
919
|
+
if (options?.failure) {
|
|
920
|
+
const enhancedError = new Error(`${options.failure}: ${error.message}`);
|
|
921
|
+
enhancedError.stack = error.stack;
|
|
922
|
+
throw enhancedError;
|
|
923
|
+
}
|
|
924
|
+
throw error;
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
/**
|
|
929
|
+
* Inline mode for major steps
|
|
930
|
+
*/
|
|
931
|
+
async majorInline(title, action, options) {
|
|
932
|
+
const stepTitle = this.formatStepTitle(title, "major", options);
|
|
933
|
+
return import_test.test.step(stepTitle, async () => {
|
|
934
|
+
try {
|
|
935
|
+
await action();
|
|
936
|
+
} catch (error) {
|
|
937
|
+
if (options?.failure) {
|
|
938
|
+
const enhancedError = new Error(`${options.failure}: ${error.message}`);
|
|
939
|
+
enhancedError.stack = error.stack;
|
|
940
|
+
throw enhancedError;
|
|
941
|
+
}
|
|
942
|
+
throw error;
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* Declarative mode for major steps with child steps
|
|
948
|
+
*/
|
|
949
|
+
async majorDeclarative(title, options) {
|
|
950
|
+
const stepTitle = this.formatStepTitle(title, "major", options);
|
|
951
|
+
return import_test.test.step(stepTitle, async () => {
|
|
952
|
+
if (!options.steps || options.steps.length === 0) {
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
for (const childStep of options.steps) {
|
|
956
|
+
const childTitle = this.formatStepTitle(childStep.title, "minor", childStep);
|
|
957
|
+
await import_test.test.step(childTitle, async () => {
|
|
958
|
+
try {
|
|
959
|
+
await childStep.action();
|
|
960
|
+
} catch (error) {
|
|
961
|
+
if (childStep.failure) {
|
|
962
|
+
const enhancedError = new Error(
|
|
963
|
+
`${childStep.failure}: ${error.message}`
|
|
964
|
+
);
|
|
965
|
+
enhancedError.stack = error.stack;
|
|
966
|
+
throw enhancedError;
|
|
967
|
+
}
|
|
968
|
+
throw error;
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Format step title with metadata for the reporter
|
|
976
|
+
* The reporter will parse this metadata to classify steps
|
|
977
|
+
*/
|
|
978
|
+
formatStepTitle(title, level, _options) {
|
|
979
|
+
const prefix = level === "major" ? "[MAJOR]" : "[MINOR]";
|
|
980
|
+
return `${prefix} ${title}`;
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
var e2e = new E2EHelperImpl();
|
|
984
|
+
|
|
985
|
+
// src/mcp/server.ts
|
|
986
|
+
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
987
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
988
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
989
|
+
var import_promises3 = require("fs/promises");
|
|
990
|
+
var import_fs = require("fs");
|
|
991
|
+
var MCPServer = class {
|
|
992
|
+
server;
|
|
993
|
+
config;
|
|
994
|
+
testResults = [];
|
|
995
|
+
constructor(config = {}) {
|
|
996
|
+
this.config = {
|
|
997
|
+
resultsPath: config.resultsPath ?? "./test-results/results.json",
|
|
998
|
+
name: config.name ?? "fair-playwright",
|
|
999
|
+
version: config.version ?? "0.1.0",
|
|
1000
|
+
verbose: config.verbose ?? false
|
|
1001
|
+
};
|
|
1002
|
+
this.server = new import_server.Server(
|
|
1003
|
+
{
|
|
1004
|
+
name: this.config.name,
|
|
1005
|
+
version: this.config.version
|
|
1006
|
+
},
|
|
1007
|
+
{
|
|
1008
|
+
capabilities: {
|
|
1009
|
+
resources: {},
|
|
1010
|
+
tools: {}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
);
|
|
1014
|
+
this.setupHandlers();
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Setup MCP protocol handlers
|
|
1018
|
+
*/
|
|
1019
|
+
setupHandlers() {
|
|
1020
|
+
this.server.setRequestHandler(import_types.ListResourcesRequestSchema, async () => ({
|
|
1021
|
+
resources: [
|
|
1022
|
+
{
|
|
1023
|
+
uri: "fair-playwright://test-results",
|
|
1024
|
+
name: "Test Results",
|
|
1025
|
+
description: "Current Playwright test execution results",
|
|
1026
|
+
mimeType: "application/json"
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
uri: "fair-playwright://test-summary",
|
|
1030
|
+
name: "Test Summary",
|
|
1031
|
+
description: "AI-optimized summary of test results",
|
|
1032
|
+
mimeType: "text/markdown"
|
|
1033
|
+
},
|
|
1034
|
+
{
|
|
1035
|
+
uri: "fair-playwright://failures",
|
|
1036
|
+
name: "Failed Tests",
|
|
1037
|
+
description: "Detailed information about failed tests",
|
|
1038
|
+
mimeType: "text/markdown"
|
|
1039
|
+
}
|
|
1040
|
+
]
|
|
1041
|
+
}));
|
|
1042
|
+
this.server.setRequestHandler(import_types.ReadResourceRequestSchema, async (request) => {
|
|
1043
|
+
const uri = request.params.uri.toString();
|
|
1044
|
+
if (this.testResults.length === 0) {
|
|
1045
|
+
await this.loadTestResults();
|
|
1046
|
+
}
|
|
1047
|
+
switch (uri) {
|
|
1048
|
+
case "fair-playwright://test-results":
|
|
1049
|
+
return {
|
|
1050
|
+
contents: [
|
|
1051
|
+
{
|
|
1052
|
+
uri,
|
|
1053
|
+
mimeType: "application/json",
|
|
1054
|
+
text: JSON.stringify(this.getTestResults(), null, 2)
|
|
1055
|
+
}
|
|
1056
|
+
]
|
|
1057
|
+
};
|
|
1058
|
+
case "fair-playwright://test-summary":
|
|
1059
|
+
return {
|
|
1060
|
+
contents: [
|
|
1061
|
+
{
|
|
1062
|
+
uri,
|
|
1063
|
+
mimeType: "text/markdown",
|
|
1064
|
+
text: this.getTestSummary()
|
|
1065
|
+
}
|
|
1066
|
+
]
|
|
1067
|
+
};
|
|
1068
|
+
case "fair-playwright://failures":
|
|
1069
|
+
return {
|
|
1070
|
+
contents: [
|
|
1071
|
+
{
|
|
1072
|
+
uri,
|
|
1073
|
+
mimeType: "text/markdown",
|
|
1074
|
+
text: this.getFailureSummary()
|
|
1075
|
+
}
|
|
1076
|
+
]
|
|
1077
|
+
};
|
|
1078
|
+
default:
|
|
1079
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
this.server.setRequestHandler(import_types.ListToolsRequestSchema, async () => ({
|
|
1083
|
+
tools: [
|
|
1084
|
+
{
|
|
1085
|
+
name: "get_test_results",
|
|
1086
|
+
description: "Get complete test execution results with all details",
|
|
1087
|
+
inputSchema: {
|
|
1088
|
+
type: "object",
|
|
1089
|
+
properties: {}
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
name: "get_failure_summary",
|
|
1094
|
+
description: "Get AI-optimized summary of failed tests",
|
|
1095
|
+
inputSchema: {
|
|
1096
|
+
type: "object",
|
|
1097
|
+
properties: {}
|
|
1098
|
+
}
|
|
1099
|
+
},
|
|
1100
|
+
{
|
|
1101
|
+
name: "query_test",
|
|
1102
|
+
description: "Search for a specific test by title",
|
|
1103
|
+
inputSchema: {
|
|
1104
|
+
type: "object",
|
|
1105
|
+
properties: {
|
|
1106
|
+
title: {
|
|
1107
|
+
type: "string",
|
|
1108
|
+
description: "Test title to search for (case-insensitive partial match)"
|
|
1109
|
+
}
|
|
1110
|
+
},
|
|
1111
|
+
required: ["title"]
|
|
1112
|
+
}
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
name: "get_tests_by_status",
|
|
1116
|
+
description: "Get all tests filtered by status",
|
|
1117
|
+
inputSchema: {
|
|
1118
|
+
type: "object",
|
|
1119
|
+
properties: {
|
|
1120
|
+
status: {
|
|
1121
|
+
type: "string",
|
|
1122
|
+
enum: ["passed", "failed", "skipped"],
|
|
1123
|
+
description: "Test status to filter by"
|
|
1124
|
+
}
|
|
1125
|
+
},
|
|
1126
|
+
required: ["status"]
|
|
1127
|
+
}
|
|
1128
|
+
},
|
|
1129
|
+
{
|
|
1130
|
+
name: "get_step_details",
|
|
1131
|
+
description: "Get detailed information about test steps",
|
|
1132
|
+
inputSchema: {
|
|
1133
|
+
type: "object",
|
|
1134
|
+
properties: {
|
|
1135
|
+
testTitle: {
|
|
1136
|
+
type: "string",
|
|
1137
|
+
description: "Title of the test to get step details for"
|
|
1138
|
+
},
|
|
1139
|
+
level: {
|
|
1140
|
+
type: "string",
|
|
1141
|
+
enum: ["major", "minor", "all"],
|
|
1142
|
+
description: "Filter steps by level (default: all)"
|
|
1143
|
+
}
|
|
1144
|
+
},
|
|
1145
|
+
required: ["testTitle"]
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
]
|
|
1149
|
+
}));
|
|
1150
|
+
this.server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
|
|
1151
|
+
const { name, arguments: args } = request.params;
|
|
1152
|
+
if (this.testResults.length === 0) {
|
|
1153
|
+
await this.loadTestResults();
|
|
1154
|
+
}
|
|
1155
|
+
switch (name) {
|
|
1156
|
+
case "get_test_results":
|
|
1157
|
+
return {
|
|
1158
|
+
content: [
|
|
1159
|
+
{
|
|
1160
|
+
type: "text",
|
|
1161
|
+
text: JSON.stringify(this.getTestResults(), null, 2)
|
|
1162
|
+
}
|
|
1163
|
+
]
|
|
1164
|
+
};
|
|
1165
|
+
case "get_failure_summary":
|
|
1166
|
+
return {
|
|
1167
|
+
content: [
|
|
1168
|
+
{
|
|
1169
|
+
type: "text",
|
|
1170
|
+
text: this.getFailureSummary()
|
|
1171
|
+
}
|
|
1172
|
+
]
|
|
1173
|
+
};
|
|
1174
|
+
case "query_test": {
|
|
1175
|
+
const title = args.title;
|
|
1176
|
+
const test2 = this.queryTest(title);
|
|
1177
|
+
return {
|
|
1178
|
+
content: [
|
|
1179
|
+
{
|
|
1180
|
+
type: "text",
|
|
1181
|
+
text: test2 ? JSON.stringify(test2, null, 2) : `No test found matching: ${title}`
|
|
1182
|
+
}
|
|
1183
|
+
]
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
case "get_tests_by_status": {
|
|
1187
|
+
const status = args.status;
|
|
1188
|
+
const tests = this.getTestsByStatus(status);
|
|
1189
|
+
return {
|
|
1190
|
+
content: [
|
|
1191
|
+
{
|
|
1192
|
+
type: "text",
|
|
1193
|
+
text: JSON.stringify(tests, null, 2)
|
|
1194
|
+
}
|
|
1195
|
+
]
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
case "get_step_details": {
|
|
1199
|
+
const { testTitle, level } = args;
|
|
1200
|
+
const test2 = this.queryTest(testTitle);
|
|
1201
|
+
if (!test2) {
|
|
1202
|
+
return {
|
|
1203
|
+
content: [
|
|
1204
|
+
{
|
|
1205
|
+
type: "text",
|
|
1206
|
+
text: `No test found matching: ${testTitle}`
|
|
1207
|
+
}
|
|
1208
|
+
]
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
let steps = test2.steps;
|
|
1212
|
+
if (level && level !== "all") {
|
|
1213
|
+
steps = steps.filter((s) => s.level === level);
|
|
1214
|
+
}
|
|
1215
|
+
return {
|
|
1216
|
+
content: [
|
|
1217
|
+
{
|
|
1218
|
+
type: "text",
|
|
1219
|
+
text: JSON.stringify(steps, null, 2)
|
|
1220
|
+
}
|
|
1221
|
+
]
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
default:
|
|
1225
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Load test results from JSON file
|
|
1231
|
+
*/
|
|
1232
|
+
async loadTestResults() {
|
|
1233
|
+
try {
|
|
1234
|
+
if (!(0, import_fs.existsSync)(this.config.resultsPath)) {
|
|
1235
|
+
if (this.config.verbose) {
|
|
1236
|
+
console.error(`[MCP] Results file not found: ${this.config.resultsPath}`);
|
|
1237
|
+
}
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
const content = await (0, import_promises3.readFile)(this.config.resultsPath, "utf-8");
|
|
1241
|
+
const data = JSON.parse(content);
|
|
1242
|
+
this.testResults = Array.isArray(data) ? data : data.tests || [];
|
|
1243
|
+
if (this.config.verbose) {
|
|
1244
|
+
console.error(`[MCP] Loaded ${this.testResults.length} test results`);
|
|
1245
|
+
}
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
if (this.config.verbose) {
|
|
1248
|
+
console.error(`[MCP] Error loading test results:`, error);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* Start the MCP server with stdio transport
|
|
1254
|
+
*/
|
|
1255
|
+
async start() {
|
|
1256
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
1257
|
+
await this.server.connect(transport);
|
|
1258
|
+
if (this.config.verbose) {
|
|
1259
|
+
console.error("[MCP] Server started and connected via stdio");
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Get current test results
|
|
1264
|
+
*/
|
|
1265
|
+
getTestResults() {
|
|
1266
|
+
const passed = this.testResults.filter((t) => t.status === "passed").length;
|
|
1267
|
+
const failed = this.testResults.filter((t) => t.status === "failed").length;
|
|
1268
|
+
const skipped = this.testResults.filter((t) => t.status === "skipped").length;
|
|
1269
|
+
const totalDuration = this.testResults.reduce((sum, t) => sum + (t.duration || 0), 0);
|
|
1270
|
+
return {
|
|
1271
|
+
status: failed > 0 ? "failed" : this.testResults.length > 0 ? "passed" : "unknown",
|
|
1272
|
+
summary: {
|
|
1273
|
+
total: this.testResults.length,
|
|
1274
|
+
passed,
|
|
1275
|
+
failed,
|
|
1276
|
+
skipped,
|
|
1277
|
+
duration: totalDuration
|
|
1278
|
+
},
|
|
1279
|
+
tests: this.testResults
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Get test summary in markdown format
|
|
1284
|
+
*/
|
|
1285
|
+
getTestSummary() {
|
|
1286
|
+
const results = this.getTestResults();
|
|
1287
|
+
const { summary } = results;
|
|
1288
|
+
let md = "# Playwright Test Results\n\n";
|
|
1289
|
+
md += `**Status**: ${results.status === "failed" ? "\u274C FAILED" : "\u2705 PASSED"}
|
|
1290
|
+
`;
|
|
1291
|
+
md += `**Total Tests**: ${summary.total}
|
|
1292
|
+
`;
|
|
1293
|
+
md += `**Duration**: ${(summary.duration / 1e3).toFixed(2)}s
|
|
1294
|
+
|
|
1295
|
+
`;
|
|
1296
|
+
md += "## Summary\n\n";
|
|
1297
|
+
md += `- \u2705 Passed: ${summary.passed}
|
|
1298
|
+
`;
|
|
1299
|
+
md += `- \u274C Failed: ${summary.failed}
|
|
1300
|
+
`;
|
|
1301
|
+
md += `- \u2298 Skipped: ${summary.skipped}
|
|
1302
|
+
|
|
1303
|
+
`;
|
|
1304
|
+
if (summary.failed > 0) {
|
|
1305
|
+
md += "## Failed Tests\n\n";
|
|
1306
|
+
const failedTests = this.testResults.filter((t) => t.status === "failed");
|
|
1307
|
+
failedTests.forEach((test2, index) => {
|
|
1308
|
+
md += `${index + 1}. **${test2.title}** (${test2.duration}ms)
|
|
1309
|
+
`;
|
|
1310
|
+
md += ` - File: \`${test2.file}\`
|
|
1311
|
+
`;
|
|
1312
|
+
if (test2.error) {
|
|
1313
|
+
md += ` - Error: ${test2.error.message}
|
|
1314
|
+
`;
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
return md;
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Get AI-optimized summary of failures
|
|
1322
|
+
*/
|
|
1323
|
+
getFailureSummary() {
|
|
1324
|
+
const failedTests = this.testResults.filter((t) => t.status === "failed");
|
|
1325
|
+
if (failedTests.length === 0) {
|
|
1326
|
+
return "# No Test Failures\n\nAll tests passed! \u2705";
|
|
1327
|
+
}
|
|
1328
|
+
let summary = `# Test Failures Summary
|
|
1329
|
+
|
|
1330
|
+
`;
|
|
1331
|
+
summary += `${failedTests.length} test(s) failed:
|
|
1332
|
+
|
|
1333
|
+
`;
|
|
1334
|
+
failedTests.forEach((test2, index) => {
|
|
1335
|
+
summary += `## ${index + 1}. ${test2.title}
|
|
1336
|
+
|
|
1337
|
+
`;
|
|
1338
|
+
summary += `**File**: \`${test2.file}\`
|
|
1339
|
+
`;
|
|
1340
|
+
summary += `**Duration**: ${test2.duration}ms
|
|
1341
|
+
|
|
1342
|
+
`;
|
|
1343
|
+
if (test2.error) {
|
|
1344
|
+
summary += `**Error**: ${test2.error.message}
|
|
1345
|
+
|
|
1346
|
+
`;
|
|
1347
|
+
if (test2.error.location) {
|
|
1348
|
+
summary += `**Location**: \`${test2.error.location}\`
|
|
1349
|
+
|
|
1350
|
+
`;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
const majorSteps = test2.steps.filter((s) => s.level === "major" && !s.parentId);
|
|
1354
|
+
if (majorSteps.length > 0) {
|
|
1355
|
+
summary += `**Test Flow**:
|
|
1356
|
+
|
|
1357
|
+
`;
|
|
1358
|
+
majorSteps.forEach((majorStep, idx) => {
|
|
1359
|
+
const icon = majorStep.status === "passed" ? "\u2705" : "\u274C";
|
|
1360
|
+
summary += `${idx + 1}. ${icon} [MAJOR] ${majorStep.title}
|
|
1361
|
+
`;
|
|
1362
|
+
if (majorStep.status === "failed") {
|
|
1363
|
+
const minorSteps = test2.steps.filter((s) => s.parentId === majorStep.id);
|
|
1364
|
+
minorSteps.forEach((minorStep) => {
|
|
1365
|
+
const minorIcon = minorStep.status === "passed" ? "\u2705" : "\u274C";
|
|
1366
|
+
summary += ` - ${minorIcon} [minor] ${minorStep.title}
|
|
1367
|
+
`;
|
|
1368
|
+
if (minorStep.error) {
|
|
1369
|
+
summary += ` Error: ${minorStep.error.message}
|
|
1370
|
+
`;
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
summary += `
|
|
1376
|
+
`;
|
|
1377
|
+
}
|
|
1378
|
+
if (test2.consoleErrors && test2.consoleErrors.length > 0) {
|
|
1379
|
+
summary += `**Browser Console Errors** (${test2.consoleErrors.length}):
|
|
1380
|
+
|
|
1381
|
+
`;
|
|
1382
|
+
test2.consoleErrors.forEach((consoleError, idx) => {
|
|
1383
|
+
summary += `${idx + 1}. [${consoleError.type}] ${consoleError.message}
|
|
1384
|
+
`;
|
|
1385
|
+
});
|
|
1386
|
+
summary += `
|
|
1387
|
+
`;
|
|
1388
|
+
}
|
|
1389
|
+
summary += `---
|
|
1390
|
+
|
|
1391
|
+
`;
|
|
1392
|
+
});
|
|
1393
|
+
return summary;
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Query specific test by title
|
|
1397
|
+
*/
|
|
1398
|
+
queryTest(title) {
|
|
1399
|
+
const test2 = this.testResults.find(
|
|
1400
|
+
(t) => t.title.toLowerCase().includes(title.toLowerCase())
|
|
1401
|
+
);
|
|
1402
|
+
return test2 || null;
|
|
1403
|
+
}
|
|
1404
|
+
/**
|
|
1405
|
+
* Get tests by status
|
|
1406
|
+
*/
|
|
1407
|
+
getTestsByStatus(status) {
|
|
1408
|
+
return this.testResults.filter((t) => t.status === status);
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
async function createMCPServer(config) {
|
|
1412
|
+
const server = new MCPServer(config);
|
|
1413
|
+
await server.start();
|
|
1414
|
+
return server;
|
|
1415
|
+
}
|
|
1416
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1417
|
+
0 && (module.exports = {
|
|
1418
|
+
FairReporter,
|
|
1419
|
+
MCPServer,
|
|
1420
|
+
createMCPServer,
|
|
1421
|
+
e2e
|
|
1422
|
+
});
|