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/mcp-cli.js
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/mcp/server.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
ListResourcesRequestSchema,
|
|
9
|
+
ListToolsRequestSchema,
|
|
10
|
+
ReadResourceRequestSchema
|
|
11
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
import { readFile } from "fs/promises";
|
|
13
|
+
import { existsSync } from "fs";
|
|
14
|
+
var MCPServer = class {
|
|
15
|
+
server;
|
|
16
|
+
config;
|
|
17
|
+
testResults = [];
|
|
18
|
+
constructor(config = {}) {
|
|
19
|
+
this.config = {
|
|
20
|
+
resultsPath: config.resultsPath ?? "./test-results/results.json",
|
|
21
|
+
name: config.name ?? "fair-playwright",
|
|
22
|
+
version: config.version ?? "0.1.0",
|
|
23
|
+
verbose: config.verbose ?? false
|
|
24
|
+
};
|
|
25
|
+
this.server = new Server(
|
|
26
|
+
{
|
|
27
|
+
name: this.config.name,
|
|
28
|
+
version: this.config.version
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
capabilities: {
|
|
32
|
+
resources: {},
|
|
33
|
+
tools: {}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
this.setupHandlers();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Setup MCP protocol handlers
|
|
41
|
+
*/
|
|
42
|
+
setupHandlers() {
|
|
43
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
44
|
+
resources: [
|
|
45
|
+
{
|
|
46
|
+
uri: "fair-playwright://test-results",
|
|
47
|
+
name: "Test Results",
|
|
48
|
+
description: "Current Playwright test execution results",
|
|
49
|
+
mimeType: "application/json"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
uri: "fair-playwright://test-summary",
|
|
53
|
+
name: "Test Summary",
|
|
54
|
+
description: "AI-optimized summary of test results",
|
|
55
|
+
mimeType: "text/markdown"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
uri: "fair-playwright://failures",
|
|
59
|
+
name: "Failed Tests",
|
|
60
|
+
description: "Detailed information about failed tests",
|
|
61
|
+
mimeType: "text/markdown"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}));
|
|
65
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
66
|
+
const uri = request.params.uri.toString();
|
|
67
|
+
if (this.testResults.length === 0) {
|
|
68
|
+
await this.loadTestResults();
|
|
69
|
+
}
|
|
70
|
+
switch (uri) {
|
|
71
|
+
case "fair-playwright://test-results":
|
|
72
|
+
return {
|
|
73
|
+
contents: [
|
|
74
|
+
{
|
|
75
|
+
uri,
|
|
76
|
+
mimeType: "application/json",
|
|
77
|
+
text: JSON.stringify(this.getTestResults(), null, 2)
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
};
|
|
81
|
+
case "fair-playwright://test-summary":
|
|
82
|
+
return {
|
|
83
|
+
contents: [
|
|
84
|
+
{
|
|
85
|
+
uri,
|
|
86
|
+
mimeType: "text/markdown",
|
|
87
|
+
text: this.getTestSummary()
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
};
|
|
91
|
+
case "fair-playwright://failures":
|
|
92
|
+
return {
|
|
93
|
+
contents: [
|
|
94
|
+
{
|
|
95
|
+
uri,
|
|
96
|
+
mimeType: "text/markdown",
|
|
97
|
+
text: this.getFailureSummary()
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
};
|
|
101
|
+
default:
|
|
102
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
106
|
+
tools: [
|
|
107
|
+
{
|
|
108
|
+
name: "get_test_results",
|
|
109
|
+
description: "Get complete test execution results with all details",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "get_failure_summary",
|
|
117
|
+
description: "Get AI-optimized summary of failed tests",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "query_test",
|
|
125
|
+
description: "Search for a specific test by title",
|
|
126
|
+
inputSchema: {
|
|
127
|
+
type: "object",
|
|
128
|
+
properties: {
|
|
129
|
+
title: {
|
|
130
|
+
type: "string",
|
|
131
|
+
description: "Test title to search for (case-insensitive partial match)"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
required: ["title"]
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "get_tests_by_status",
|
|
139
|
+
description: "Get all tests filtered by status",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
status: {
|
|
144
|
+
type: "string",
|
|
145
|
+
enum: ["passed", "failed", "skipped"],
|
|
146
|
+
description: "Test status to filter by"
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
required: ["status"]
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "get_step_details",
|
|
154
|
+
description: "Get detailed information about test steps",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
testTitle: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description: "Title of the test to get step details for"
|
|
161
|
+
},
|
|
162
|
+
level: {
|
|
163
|
+
type: "string",
|
|
164
|
+
enum: ["major", "minor", "all"],
|
|
165
|
+
description: "Filter steps by level (default: all)"
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
required: ["testTitle"]
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
}));
|
|
173
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
174
|
+
const { name, arguments: args } = request.params;
|
|
175
|
+
if (this.testResults.length === 0) {
|
|
176
|
+
await this.loadTestResults();
|
|
177
|
+
}
|
|
178
|
+
switch (name) {
|
|
179
|
+
case "get_test_results":
|
|
180
|
+
return {
|
|
181
|
+
content: [
|
|
182
|
+
{
|
|
183
|
+
type: "text",
|
|
184
|
+
text: JSON.stringify(this.getTestResults(), null, 2)
|
|
185
|
+
}
|
|
186
|
+
]
|
|
187
|
+
};
|
|
188
|
+
case "get_failure_summary":
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: "text",
|
|
193
|
+
text: this.getFailureSummary()
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
};
|
|
197
|
+
case "query_test": {
|
|
198
|
+
const title = args.title;
|
|
199
|
+
const test = this.queryTest(title);
|
|
200
|
+
return {
|
|
201
|
+
content: [
|
|
202
|
+
{
|
|
203
|
+
type: "text",
|
|
204
|
+
text: test ? JSON.stringify(test, null, 2) : `No test found matching: ${title}`
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
case "get_tests_by_status": {
|
|
210
|
+
const status = args.status;
|
|
211
|
+
const tests = this.getTestsByStatus(status);
|
|
212
|
+
return {
|
|
213
|
+
content: [
|
|
214
|
+
{
|
|
215
|
+
type: "text",
|
|
216
|
+
text: JSON.stringify(tests, null, 2)
|
|
217
|
+
}
|
|
218
|
+
]
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
case "get_step_details": {
|
|
222
|
+
const { testTitle, level } = args;
|
|
223
|
+
const test = this.queryTest(testTitle);
|
|
224
|
+
if (!test) {
|
|
225
|
+
return {
|
|
226
|
+
content: [
|
|
227
|
+
{
|
|
228
|
+
type: "text",
|
|
229
|
+
text: `No test found matching: ${testTitle}`
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
let steps = test.steps;
|
|
235
|
+
if (level && level !== "all") {
|
|
236
|
+
steps = steps.filter((s) => s.level === level);
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
content: [
|
|
240
|
+
{
|
|
241
|
+
type: "text",
|
|
242
|
+
text: JSON.stringify(steps, null, 2)
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
default:
|
|
248
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Load test results from JSON file
|
|
254
|
+
*/
|
|
255
|
+
async loadTestResults() {
|
|
256
|
+
try {
|
|
257
|
+
if (!existsSync(this.config.resultsPath)) {
|
|
258
|
+
if (this.config.verbose) {
|
|
259
|
+
console.error(`[MCP] Results file not found: ${this.config.resultsPath}`);
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const content = await readFile(this.config.resultsPath, "utf-8");
|
|
264
|
+
const data = JSON.parse(content);
|
|
265
|
+
this.testResults = Array.isArray(data) ? data : data.tests || [];
|
|
266
|
+
if (this.config.verbose) {
|
|
267
|
+
console.error(`[MCP] Loaded ${this.testResults.length} test results`);
|
|
268
|
+
}
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (this.config.verbose) {
|
|
271
|
+
console.error(`[MCP] Error loading test results:`, error);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Start the MCP server with stdio transport
|
|
277
|
+
*/
|
|
278
|
+
async start() {
|
|
279
|
+
const transport = new StdioServerTransport();
|
|
280
|
+
await this.server.connect(transport);
|
|
281
|
+
if (this.config.verbose) {
|
|
282
|
+
console.error("[MCP] Server started and connected via stdio");
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Get current test results
|
|
287
|
+
*/
|
|
288
|
+
getTestResults() {
|
|
289
|
+
const passed = this.testResults.filter((t) => t.status === "passed").length;
|
|
290
|
+
const failed = this.testResults.filter((t) => t.status === "failed").length;
|
|
291
|
+
const skipped = this.testResults.filter((t) => t.status === "skipped").length;
|
|
292
|
+
const totalDuration = this.testResults.reduce((sum, t) => sum + (t.duration || 0), 0);
|
|
293
|
+
return {
|
|
294
|
+
status: failed > 0 ? "failed" : this.testResults.length > 0 ? "passed" : "unknown",
|
|
295
|
+
summary: {
|
|
296
|
+
total: this.testResults.length,
|
|
297
|
+
passed,
|
|
298
|
+
failed,
|
|
299
|
+
skipped,
|
|
300
|
+
duration: totalDuration
|
|
301
|
+
},
|
|
302
|
+
tests: this.testResults
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Get test summary in markdown format
|
|
307
|
+
*/
|
|
308
|
+
getTestSummary() {
|
|
309
|
+
const results = this.getTestResults();
|
|
310
|
+
const { summary } = results;
|
|
311
|
+
let md = "# Playwright Test Results\n\n";
|
|
312
|
+
md += `**Status**: ${results.status === "failed" ? "\u274C FAILED" : "\u2705 PASSED"}
|
|
313
|
+
`;
|
|
314
|
+
md += `**Total Tests**: ${summary.total}
|
|
315
|
+
`;
|
|
316
|
+
md += `**Duration**: ${(summary.duration / 1e3).toFixed(2)}s
|
|
317
|
+
|
|
318
|
+
`;
|
|
319
|
+
md += "## Summary\n\n";
|
|
320
|
+
md += `- \u2705 Passed: ${summary.passed}
|
|
321
|
+
`;
|
|
322
|
+
md += `- \u274C Failed: ${summary.failed}
|
|
323
|
+
`;
|
|
324
|
+
md += `- \u2298 Skipped: ${summary.skipped}
|
|
325
|
+
|
|
326
|
+
`;
|
|
327
|
+
if (summary.failed > 0) {
|
|
328
|
+
md += "## Failed Tests\n\n";
|
|
329
|
+
const failedTests = this.testResults.filter((t) => t.status === "failed");
|
|
330
|
+
failedTests.forEach((test, index) => {
|
|
331
|
+
md += `${index + 1}. **${test.title}** (${test.duration}ms)
|
|
332
|
+
`;
|
|
333
|
+
md += ` - File: \`${test.file}\`
|
|
334
|
+
`;
|
|
335
|
+
if (test.error) {
|
|
336
|
+
md += ` - Error: ${test.error.message}
|
|
337
|
+
`;
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return md;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Get AI-optimized summary of failures
|
|
345
|
+
*/
|
|
346
|
+
getFailureSummary() {
|
|
347
|
+
const failedTests = this.testResults.filter((t) => t.status === "failed");
|
|
348
|
+
if (failedTests.length === 0) {
|
|
349
|
+
return "# No Test Failures\n\nAll tests passed! \u2705";
|
|
350
|
+
}
|
|
351
|
+
let summary = `# Test Failures Summary
|
|
352
|
+
|
|
353
|
+
`;
|
|
354
|
+
summary += `${failedTests.length} test(s) failed:
|
|
355
|
+
|
|
356
|
+
`;
|
|
357
|
+
failedTests.forEach((test, index) => {
|
|
358
|
+
summary += `## ${index + 1}. ${test.title}
|
|
359
|
+
|
|
360
|
+
`;
|
|
361
|
+
summary += `**File**: \`${test.file}\`
|
|
362
|
+
`;
|
|
363
|
+
summary += `**Duration**: ${test.duration}ms
|
|
364
|
+
|
|
365
|
+
`;
|
|
366
|
+
if (test.error) {
|
|
367
|
+
summary += `**Error**: ${test.error.message}
|
|
368
|
+
|
|
369
|
+
`;
|
|
370
|
+
if (test.error.location) {
|
|
371
|
+
summary += `**Location**: \`${test.error.location}\`
|
|
372
|
+
|
|
373
|
+
`;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const majorSteps = test.steps.filter((s) => s.level === "major" && !s.parentId);
|
|
377
|
+
if (majorSteps.length > 0) {
|
|
378
|
+
summary += `**Test Flow**:
|
|
379
|
+
|
|
380
|
+
`;
|
|
381
|
+
majorSteps.forEach((majorStep, idx) => {
|
|
382
|
+
const icon = majorStep.status === "passed" ? "\u2705" : "\u274C";
|
|
383
|
+
summary += `${idx + 1}. ${icon} [MAJOR] ${majorStep.title}
|
|
384
|
+
`;
|
|
385
|
+
if (majorStep.status === "failed") {
|
|
386
|
+
const minorSteps = test.steps.filter((s) => s.parentId === majorStep.id);
|
|
387
|
+
minorSteps.forEach((minorStep) => {
|
|
388
|
+
const minorIcon = minorStep.status === "passed" ? "\u2705" : "\u274C";
|
|
389
|
+
summary += ` - ${minorIcon} [minor] ${minorStep.title}
|
|
390
|
+
`;
|
|
391
|
+
if (minorStep.error) {
|
|
392
|
+
summary += ` Error: ${minorStep.error.message}
|
|
393
|
+
`;
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
summary += `
|
|
399
|
+
`;
|
|
400
|
+
}
|
|
401
|
+
if (test.consoleErrors && test.consoleErrors.length > 0) {
|
|
402
|
+
summary += `**Browser Console Errors** (${test.consoleErrors.length}):
|
|
403
|
+
|
|
404
|
+
`;
|
|
405
|
+
test.consoleErrors.forEach((consoleError, idx) => {
|
|
406
|
+
summary += `${idx + 1}. [${consoleError.type}] ${consoleError.message}
|
|
407
|
+
`;
|
|
408
|
+
});
|
|
409
|
+
summary += `
|
|
410
|
+
`;
|
|
411
|
+
}
|
|
412
|
+
summary += `---
|
|
413
|
+
|
|
414
|
+
`;
|
|
415
|
+
});
|
|
416
|
+
return summary;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Query specific test by title
|
|
420
|
+
*/
|
|
421
|
+
queryTest(title) {
|
|
422
|
+
const test = this.testResults.find(
|
|
423
|
+
(t) => t.title.toLowerCase().includes(title.toLowerCase())
|
|
424
|
+
);
|
|
425
|
+
return test || null;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Get tests by status
|
|
429
|
+
*/
|
|
430
|
+
getTestsByStatus(status) {
|
|
431
|
+
return this.testResults.filter((t) => t.status === status);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
async function createMCPServer(config) {
|
|
435
|
+
const server = new MCPServer(config);
|
|
436
|
+
await server.start();
|
|
437
|
+
return server;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// src/mcp/cli.ts
|
|
441
|
+
async function main() {
|
|
442
|
+
const args = process.argv.slice(2);
|
|
443
|
+
const config = {};
|
|
444
|
+
for (let i = 0; i < args.length; i++) {
|
|
445
|
+
const arg = args[i];
|
|
446
|
+
switch (arg) {
|
|
447
|
+
case "--help":
|
|
448
|
+
case "-h":
|
|
449
|
+
console.log(`
|
|
450
|
+
fair-playwright MCP Server
|
|
451
|
+
|
|
452
|
+
Usage:
|
|
453
|
+
npx fair-playwright-mcp [options]
|
|
454
|
+
|
|
455
|
+
Options:
|
|
456
|
+
--results-path <path> Path to test results JSON file
|
|
457
|
+
Default: ./test-results/results.json
|
|
458
|
+
--verbose Enable verbose logging
|
|
459
|
+
--help Show this help message
|
|
460
|
+
|
|
461
|
+
Example:
|
|
462
|
+
npx fair-playwright-mcp --results-path ./custom-results.json
|
|
463
|
+
|
|
464
|
+
For Claude Desktop integration, add to claude_desktop_config.json:
|
|
465
|
+
{
|
|
466
|
+
"mcpServers": {
|
|
467
|
+
"fair-playwright": {
|
|
468
|
+
"command": "npx",
|
|
469
|
+
"args": ["fair-playwright-mcp"]
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
`);
|
|
474
|
+
process.exit(0);
|
|
475
|
+
break;
|
|
476
|
+
case "--results-path":
|
|
477
|
+
config.resultsPath = args[++i];
|
|
478
|
+
break;
|
|
479
|
+
case "--verbose":
|
|
480
|
+
config.verbose = true;
|
|
481
|
+
break;
|
|
482
|
+
default:
|
|
483
|
+
console.error(`Unknown option: ${arg}`);
|
|
484
|
+
console.error(`Run with --help for usage information`);
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (process.env.FAIR_PLAYWRIGHT_RESULTS && !config.resultsPath) {
|
|
489
|
+
config.resultsPath = process.env.FAIR_PLAYWRIGHT_RESULTS;
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
await createMCPServer(config);
|
|
493
|
+
process.on("SIGINT", () => {
|
|
494
|
+
if (config.verbose) {
|
|
495
|
+
console.error("\n[MCP] Shutting down...");
|
|
496
|
+
}
|
|
497
|
+
process.exit(0);
|
|
498
|
+
});
|
|
499
|
+
} catch (error) {
|
|
500
|
+
console.error("Failed to start MCP server:", error);
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fair-playwright",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-optimized Playwright test reporter with progressive terminal output and hierarchical step management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"fair-playwright-mcp": "./dist/mcp-cli.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.mjs",
|
|
16
|
+
"require": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"build:watch": "tsup --watch",
|
|
27
|
+
"dev": "tsup --watch",
|
|
28
|
+
"test": "vitest",
|
|
29
|
+
"test:watch": "vitest --watch",
|
|
30
|
+
"test:integration": "npm run build && cd test-project && npm test",
|
|
31
|
+
"lint": "eslint src --ext .ts",
|
|
32
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
33
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
34
|
+
"typecheck": "tsc --noEmit",
|
|
35
|
+
"changeset": "changeset",
|
|
36
|
+
"release": "changeset publish",
|
|
37
|
+
"prepublishOnly": "npm run build"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"playwright",
|
|
41
|
+
"testing",
|
|
42
|
+
"reporter",
|
|
43
|
+
"ai",
|
|
44
|
+
"test-automation",
|
|
45
|
+
"e2e",
|
|
46
|
+
"terminal",
|
|
47
|
+
"progressive",
|
|
48
|
+
"ai-optimized",
|
|
49
|
+
"mcp",
|
|
50
|
+
"model-context-protocol",
|
|
51
|
+
"llm",
|
|
52
|
+
"claude"
|
|
53
|
+
],
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "https://github.com/baranaytass/fair-playwright.git"
|
|
57
|
+
},
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/baranaytass/fair-playwright/issues"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://github.com/baranaytass/fair-playwright#readme",
|
|
62
|
+
"author": "Baran Aytas",
|
|
63
|
+
"license": "MIT",
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@playwright/test": ">=1.40.0"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
69
|
+
"log-update": "^6.1.0",
|
|
70
|
+
"picocolors": "^1.1.1"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@changesets/cli": "^2.27.10",
|
|
74
|
+
"@playwright/test": "^1.49.1",
|
|
75
|
+
"@types/node": "^22.10.2",
|
|
76
|
+
"@typescript-eslint/eslint-plugin": "^8.18.1",
|
|
77
|
+
"@typescript-eslint/parser": "^8.18.1",
|
|
78
|
+
"eslint": "^9.17.0",
|
|
79
|
+
"eslint-config-prettier": "^9.1.0",
|
|
80
|
+
"prettier": "^3.4.2",
|
|
81
|
+
"tsup": "^8.3.5",
|
|
82
|
+
"typescript": "^5.7.2",
|
|
83
|
+
"vitest": "^2.1.8"
|
|
84
|
+
},
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=18"
|
|
87
|
+
}
|
|
88
|
+
}
|