donobu 2.29.1 → 2.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/generated/version +1 -1
- package/dist/cli/install-donobu-plugin.d.ts +6 -0
- package/dist/cli/install-donobu-plugin.d.ts.map +1 -0
- package/dist/cli/install-donobu-plugin.js +125 -0
- package/dist/cli/install-donobu-plugin.js.map +1 -0
- package/dist/cli/playwright-json-to-markdown.d.ts +43 -0
- package/dist/cli/playwright-json-to-markdown.d.ts.map +1 -0
- package/dist/cli/playwright-json-to-markdown.js +239 -0
- package/dist/cli/playwright-json-to-markdown.js.map +1 -0
- package/dist/cli/playwright-json-to-slack-json.d.ts +3 -0
- package/dist/cli/playwright-json-to-slack-json.d.ts.map +1 -0
- package/dist/cli/playwright-json-to-slack-json.js +207 -0
- package/dist/cli/playwright-json-to-slack-json.js.map +1 -0
- package/dist/esm/assets/generated/version +1 -1
- package/dist/esm/cli/install-donobu-plugin.d.ts +6 -0
- package/dist/esm/cli/install-donobu-plugin.d.ts.map +1 -0
- package/dist/esm/cli/install-donobu-plugin.js +125 -0
- package/dist/esm/cli/install-donobu-plugin.js.map +1 -0
- package/dist/esm/cli/playwright-json-to-markdown.d.ts +43 -0
- package/dist/esm/cli/playwright-json-to-markdown.d.ts.map +1 -0
- package/dist/esm/cli/playwright-json-to-markdown.js +239 -0
- package/dist/esm/cli/playwright-json-to-markdown.js.map +1 -0
- package/dist/esm/cli/playwright-json-to-slack-json.d.ts +3 -0
- package/dist/esm/cli/playwright-json-to-slack-json.d.ts.map +1 -0
- package/dist/esm/cli/playwright-json-to-slack-json.js +207 -0
- package/dist/esm/cli/playwright-json-to-slack-json.js.map +1 -0
- package/dist/esm/managers/PluginLoader.js +3 -15
- package/dist/esm/managers/PluginLoader.js.map +1 -1
- package/dist/managers/PluginLoader.js +3 -15
- package/dist/managers/PluginLoader.js.map +1 -1
- package/package.json +4 -3
- package/dist/assets/playwright-json-to-markdown.js +0 -257
- package/dist/assets/playwright-json-to-slack-json.js +0 -171
- package/dist/esm/assets/playwright-json-to-markdown.js +0 -257
- package/dist/esm/assets/playwright-json-to-slack-json.js +0 -171
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
/**
|
|
5
|
+
* @fileoverview Playwright JSON Report to Slack Message Converter
|
|
6
|
+
*
|
|
7
|
+
* A command-line utility that converts Playwright JSON test reports into Slack-compatible JSON messages
|
|
8
|
+
* using Slack's Block Kit format. This tool is designed for CI/CD pipelines to send automated test
|
|
9
|
+
* notifications to Slack channels with rich formatting and actionable information.
|
|
10
|
+
*
|
|
11
|
+
* @usage
|
|
12
|
+
* Basic usage (read from file):
|
|
13
|
+
* ```bash
|
|
14
|
+
* npm exec playwright-json-to-slack-json report.json
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* With report URL link:
|
|
18
|
+
* ```bash
|
|
19
|
+
* npm exec playwright-json-to-slack-json --report-url "https://example.com/report" report.json
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Read from stdin (useful in pipelines):
|
|
23
|
+
* ```bash
|
|
24
|
+
* cat test-results/results.json | npx playwright-json-to-slack-json --report-url "$REPORT_URL"
|
|
25
|
+
* npm exec playwright test --reporter=json | npx playwright-json-to-slack-json
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @parameters
|
|
29
|
+
* - `--report-url <url>`: Optional URL to link to the full test report.
|
|
30
|
+
* - `<report.json>`: Path to Playwright JSON report file (if not reading from stdin).
|
|
31
|
+
*
|
|
32
|
+
* @input
|
|
33
|
+
* Expects a Playwright JSON report with the following structure:
|
|
34
|
+
* - config: Test configuration including projects.
|
|
35
|
+
* - suites: Array of test suites containing specs and tests.
|
|
36
|
+
* - stats: Summary statistics (duration, expected, unexpected, flaky, skipped).
|
|
37
|
+
*
|
|
38
|
+
* @output
|
|
39
|
+
* Generates a Slack Block Kit JSON message containing:
|
|
40
|
+
* - Header with overall test status (✅ passed or ❌ failed).
|
|
41
|
+
* - Summary section with test counts and duration.
|
|
42
|
+
* - Failed test details (limited to first 10 failures).
|
|
43
|
+
* - Optional link to full report if --report-url provided.
|
|
44
|
+
* - Rich formatting with emojis and structured layout.
|
|
45
|
+
*
|
|
46
|
+
* @features
|
|
47
|
+
* - Slack Block Kit formatted messages for rich display.
|
|
48
|
+
* - Color-coded status indicators (green for pass, red for fail).
|
|
49
|
+
* - Condensed failed test listing (shows first 10, indicates if more).
|
|
50
|
+
* - Human-readable duration formatting.
|
|
51
|
+
* - Optional external report linking.
|
|
52
|
+
* - Proper emoji usage for visual clarity.
|
|
53
|
+
*
|
|
54
|
+
* @integration
|
|
55
|
+
* Use with Slack Incoming Webhooks:
|
|
56
|
+
* ```bash
|
|
57
|
+
* # Generate Slack payload and send to webhook
|
|
58
|
+
* SLACK_PAYLOAD=$(npm exec playwright-json-to-slack-json --report-url "$REPORT_URL" < results.json)
|
|
59
|
+
* curl -X POST -H 'Content-type: application/json' --data "$SLACK_PAYLOAD" "$SLACK_WEBHOOK_URL"
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
const fs_1 = require("fs");
|
|
63
|
+
// Read the JSON data from file or stdin
|
|
64
|
+
function readInput() {
|
|
65
|
+
const args = process.argv.slice(2);
|
|
66
|
+
// Parse arguments to extract report URL and input file
|
|
67
|
+
let reportUrl = null;
|
|
68
|
+
let inputFile = null;
|
|
69
|
+
for (let i = 0; i < args.length; i++) {
|
|
70
|
+
if (args[i] === '--report-url' && i + 1 < args.length) {
|
|
71
|
+
reportUrl = args[i + 1];
|
|
72
|
+
i++; // Skip the URL value
|
|
73
|
+
}
|
|
74
|
+
else if (!args[i].startsWith('--')) {
|
|
75
|
+
inputFile = args[i];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
let jsonData;
|
|
79
|
+
if (inputFile) {
|
|
80
|
+
jsonData = JSON.parse((0, fs_1.readFileSync)(inputFile, 'utf8'));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
jsonData = JSON.parse((0, fs_1.readFileSync)(0, 'utf8')); // Read from stdin
|
|
84
|
+
}
|
|
85
|
+
return { jsonData, reportUrl };
|
|
86
|
+
}
|
|
87
|
+
// Process JSON and create simplified Slack blocks
|
|
88
|
+
function generateSlackBlocks(jsonData, reportUrl) {
|
|
89
|
+
const { suites } = jsonData;
|
|
90
|
+
const blocks = [];
|
|
91
|
+
// Header block
|
|
92
|
+
blocks.push({
|
|
93
|
+
type: 'header',
|
|
94
|
+
text: {
|
|
95
|
+
type: 'plain_text',
|
|
96
|
+
text: '🎭 Playwright Test Summary',
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
// Track totals
|
|
100
|
+
let totalPassed = 0;
|
|
101
|
+
let totalFailed = 0;
|
|
102
|
+
let totalTimedOut = 0;
|
|
103
|
+
let totalSkipped = 0;
|
|
104
|
+
let totalInterrupted = 0;
|
|
105
|
+
let totalSelfHealed = 0;
|
|
106
|
+
// Process each suite to get totals
|
|
107
|
+
suites.forEach((suite) => {
|
|
108
|
+
suite.specs.forEach((spec) => {
|
|
109
|
+
spec.tests.forEach((test) => {
|
|
110
|
+
const result = test.results && test.results.at(-1);
|
|
111
|
+
const isSelfHealed = test.annotations &&
|
|
112
|
+
test.annotations.some((a) => a.type === 'self-healed');
|
|
113
|
+
if (test.status === 'skipped' ||
|
|
114
|
+
(!result && test.status === undefined)) {
|
|
115
|
+
totalSkipped++;
|
|
116
|
+
}
|
|
117
|
+
else if (result) {
|
|
118
|
+
if (isSelfHealed) {
|
|
119
|
+
totalSelfHealed++;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
switch (result.status) {
|
|
123
|
+
case 'passed':
|
|
124
|
+
totalPassed++;
|
|
125
|
+
break;
|
|
126
|
+
case 'failed':
|
|
127
|
+
totalFailed++;
|
|
128
|
+
break;
|
|
129
|
+
case 'timedOut':
|
|
130
|
+
totalTimedOut++;
|
|
131
|
+
break;
|
|
132
|
+
case 'skipped':
|
|
133
|
+
totalSkipped++;
|
|
134
|
+
break;
|
|
135
|
+
case 'interrupted':
|
|
136
|
+
totalInterrupted++;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
// Create status summary table
|
|
145
|
+
const statusRows = [
|
|
146
|
+
{ name: 'Passed', emoji: '✅', count: totalPassed },
|
|
147
|
+
{ name: 'Self-Healed', emoji: '❤️🩹', count: totalSelfHealed },
|
|
148
|
+
{ name: 'Failed', emoji: '❌', count: totalFailed },
|
|
149
|
+
{ name: 'Timed Out', emoji: '⏰', count: totalTimedOut },
|
|
150
|
+
{ name: 'Skipped', emoji: '⏭️', count: totalSkipped },
|
|
151
|
+
{ name: 'Interrupted', emoji: '⚡', count: totalInterrupted },
|
|
152
|
+
];
|
|
153
|
+
// Add each status row as a section with two fields
|
|
154
|
+
statusRows.forEach((row) => {
|
|
155
|
+
blocks.push({
|
|
156
|
+
type: 'section',
|
|
157
|
+
fields: [
|
|
158
|
+
{
|
|
159
|
+
type: 'mrkdwn',
|
|
160
|
+
text: `${row.emoji} ${row.name}`,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
type: 'mrkdwn',
|
|
164
|
+
text: `${row.count}`,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
// Add report URL link if provided
|
|
170
|
+
if (reportUrl) {
|
|
171
|
+
blocks.push({
|
|
172
|
+
type: 'divider',
|
|
173
|
+
});
|
|
174
|
+
blocks.push({
|
|
175
|
+
type: 'section',
|
|
176
|
+
text: {
|
|
177
|
+
type: 'mrkdwn',
|
|
178
|
+
text: `📊 <${reportUrl}|View Full Report>`,
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
// Add timestamp footer
|
|
183
|
+
blocks.push({
|
|
184
|
+
type: 'divider',
|
|
185
|
+
});
|
|
186
|
+
blocks.push({
|
|
187
|
+
type: 'context',
|
|
188
|
+
elements: [
|
|
189
|
+
{
|
|
190
|
+
type: 'mrkdwn',
|
|
191
|
+
text: `Report generated on ${new Date().toLocaleString()} by Donobu`,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
});
|
|
195
|
+
return { blocks };
|
|
196
|
+
}
|
|
197
|
+
// Main execution
|
|
198
|
+
try {
|
|
199
|
+
const { jsonData, reportUrl } = readInput();
|
|
200
|
+
const slackBlocks = generateSlackBlocks(jsonData, reportUrl);
|
|
201
|
+
console.log(JSON.stringify(slackBlocks, null, 2));
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
console.error('Error processing JSON:', error.message);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=playwright-json-to-slack-json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright-json-to-slack-json.js","sourceRoot":"","sources":["../../../src/cli/playwright-json-to-slack-json.ts"],"names":[],"mappings":"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"}
|
|
@@ -65,7 +65,7 @@ async function loadAllCustomToolPlugins() {
|
|
|
65
65
|
const pluginName = folder.name;
|
|
66
66
|
const pluginPath = node_path_1.default.join(pluginsDir, pluginName);
|
|
67
67
|
Logger_1.appLogger.info(`↳ Loading plugin "${pluginName}" from ${pluginPath}`);
|
|
68
|
-
const entryPoint = await getPluginEntryPoint(
|
|
68
|
+
const entryPoint = await getPluginEntryPoint(pluginPath);
|
|
69
69
|
try {
|
|
70
70
|
const mod = await import((0, node_url_1.pathToFileURL)(entryPoint).href);
|
|
71
71
|
if (mod &&
|
|
@@ -86,19 +86,7 @@ async function loadAllCustomToolPlugins() {
|
|
|
86
86
|
Logger_1.appLogger.info(`Loaded ${tools.length} custom tool(s) from plugins.`);
|
|
87
87
|
return tools;
|
|
88
88
|
}
|
|
89
|
-
async function getPluginEntryPoint(
|
|
90
|
-
|
|
91
|
-
let mainFile = defaultMainFileName;
|
|
92
|
-
try {
|
|
93
|
-
const pkgJsonContent = await promises_1.default.readFile(node_path_1.default.join(pluginPath, 'package.json'), 'utf-8');
|
|
94
|
-
const pkgJson = JSON.parse(pkgJsonContent);
|
|
95
|
-
if (pkgJson.main) {
|
|
96
|
-
mainFile = pkgJson.main;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch (_e) {
|
|
100
|
-
Logger_1.appLogger.warn(`Plugin "${pluginName}" has no package.json or it's invalid. Falling back to ${defaultMainFileName}`);
|
|
101
|
-
}
|
|
102
|
-
return node_path_1.default.join(pluginPath, mainFile);
|
|
89
|
+
async function getPluginEntryPoint(pluginPath) {
|
|
90
|
+
return node_path_1.default.join(pluginPath, 'index.mjs');
|
|
103
91
|
}
|
|
104
92
|
//# sourceMappingURL=PluginLoader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginLoader.js","sourceRoot":"","sources":["../../../src/managers/PluginLoader.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,4DAyDC;AAhFD,gEAAkC;AAClC,0DAA6B;AAC7B,uCAAyC;AAEzC,kDAA+C;AAC/C,4CAA4C;AAE5C,uDAAyC;AAgBlC,KAAK,UAAU,wBAAwB;IAG5C,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;IAC9C,MAAM,kBAAkB,GAAuB;QAC7C,MAAM,EAAE,UAAU;QAClB,UAAU,EAAE,UAAU;KACvB,CAAC;IACF,MAAM,UAAU,GAAG,mBAAI,CAAC,IAAI,CAAC,qBAAS,CAAC,oBAAoB,EAAE,EAAE,SAAS,CAAC,CAAC;IAC1E,kBAAS,CAAC,IAAI,CAAC,aAAa,UAAU,kBAAkB,CAAC,CAAC;IAC1D,IAAI,UAAoB,CAAC;IAEzB,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,kBAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,yDAAyD;QACzD,UAAU,GAAG,EAAE,CAAC;IAClB,CAAC;IAED,MAAM,aAAa,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAChE,MAAM,KAAK,GAAqB,EAAE,CAAC;IAEnC,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC;QAC/B,MAAM,UAAU,GAAG,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACrD,kBAAS,CAAC,IAAI,CAAC,qBAAqB,UAAU,UAAU,UAAU,EAAE,CAAC,CAAC;QACtE,MAAM,UAAU,GAAG,MAAM,mBAAmB,CAAC,UAAU,
|
|
1
|
+
{"version":3,"file":"PluginLoader.js","sourceRoot":"","sources":["../../../src/managers/PluginLoader.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,4DAyDC;AAhFD,gEAAkC;AAClC,0DAA6B;AAC7B,uCAAyC;AAEzC,kDAA+C;AAC/C,4CAA4C;AAE5C,uDAAyC;AAgBlC,KAAK,UAAU,wBAAwB;IAG5C,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;IAC9C,MAAM,kBAAkB,GAAuB;QAC7C,MAAM,EAAE,UAAU;QAClB,UAAU,EAAE,UAAU;KACvB,CAAC;IACF,MAAM,UAAU,GAAG,mBAAI,CAAC,IAAI,CAAC,qBAAS,CAAC,oBAAoB,EAAE,EAAE,SAAS,CAAC,CAAC;IAC1E,kBAAS,CAAC,IAAI,CAAC,aAAa,UAAU,kBAAkB,CAAC,CAAC;IAC1D,IAAI,UAAoB,CAAC;IAEzB,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,kBAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,yDAAyD;QACzD,UAAU,GAAG,EAAE,CAAC;IAClB,CAAC;IAED,MAAM,aAAa,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAChE,MAAM,KAAK,GAAqB,EAAE,CAAC;IAEnC,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC;QAC/B,MAAM,UAAU,GAAG,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACrD,kBAAS,CAAC,IAAI,CAAC,qBAAqB,UAAU,UAAU,UAAU,EAAE,CAAC,CAAC;QACtE,MAAM,UAAU,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAC;QAEzD,IAAI,CAAC;YACH,MAAM,GAAG,GAAY,MAAM,MAAM,CAAC,IAAA,wBAAa,EAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC;YAElE,IACE,GAAG;gBACH,OAAQ,GAA6B,CAAC,eAAe,KAAK,UAAU,EACpE,CAAC;gBACD,MAAM,WAAW,GAAG,MAAO,GAAoB,CAAC,eAAe,CAC7D,kBAAkB,CACnB,CAAC;gBACF,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC;gBAC3B,kBAAS,CAAC,IAAI,CACZ,cAAc,WAAW,CAAC,MAAM,yBAAyB,UAAU,IAAI,CACxE,CAAC;gBACF,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;oBACxB,kBAAS,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;gBACnD,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,kBAAS,CAAC,KAAK,CACb,WAAW,UAAU,mCAAmC,mBAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EACrF,GAAG,CACJ,CAAC;YACF,+CAA+C;QACjD,CAAC;IACH,CAAC;IAED,kBAAS,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,MAAM,+BAA+B,CAAC,CAAC;IACtE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,UAAkB;IACnD,OAAO,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -65,7 +65,7 @@ async function loadAllCustomToolPlugins() {
|
|
|
65
65
|
const pluginName = folder.name;
|
|
66
66
|
const pluginPath = node_path_1.default.join(pluginsDir, pluginName);
|
|
67
67
|
Logger_1.appLogger.info(`↳ Loading plugin "${pluginName}" from ${pluginPath}`);
|
|
68
|
-
const entryPoint = await getPluginEntryPoint(
|
|
68
|
+
const entryPoint = await getPluginEntryPoint(pluginPath);
|
|
69
69
|
try {
|
|
70
70
|
const mod = await import((0, node_url_1.pathToFileURL)(entryPoint).href);
|
|
71
71
|
if (mod &&
|
|
@@ -86,19 +86,7 @@ async function loadAllCustomToolPlugins() {
|
|
|
86
86
|
Logger_1.appLogger.info(`Loaded ${tools.length} custom tool(s) from plugins.`);
|
|
87
87
|
return tools;
|
|
88
88
|
}
|
|
89
|
-
async function getPluginEntryPoint(
|
|
90
|
-
|
|
91
|
-
let mainFile = defaultMainFileName;
|
|
92
|
-
try {
|
|
93
|
-
const pkgJsonContent = await promises_1.default.readFile(node_path_1.default.join(pluginPath, 'package.json'), 'utf-8');
|
|
94
|
-
const pkgJson = JSON.parse(pkgJsonContent);
|
|
95
|
-
if (pkgJson.main) {
|
|
96
|
-
mainFile = pkgJson.main;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch (_e) {
|
|
100
|
-
Logger_1.appLogger.warn(`Plugin "${pluginName}" has no package.json or it's invalid. Falling back to ${defaultMainFileName}`);
|
|
101
|
-
}
|
|
102
|
-
return node_path_1.default.join(pluginPath, mainFile);
|
|
89
|
+
async function getPluginEntryPoint(pluginPath) {
|
|
90
|
+
return node_path_1.default.join(pluginPath, 'index.mjs');
|
|
103
91
|
}
|
|
104
92
|
//# sourceMappingURL=PluginLoader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginLoader.js","sourceRoot":"","sources":["../../src/managers/PluginLoader.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,4DAyDC;AAhFD,gEAAkC;AAClC,0DAA6B;AAC7B,uCAAyC;AAEzC,kDAA+C;AAC/C,4CAA4C;AAE5C,uDAAyC;AAgBlC,KAAK,UAAU,wBAAwB;IAG5C,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;IAC9C,MAAM,kBAAkB,GAAuB;QAC7C,MAAM,EAAE,UAAU;QAClB,UAAU,EAAE,UAAU;KACvB,CAAC;IACF,MAAM,UAAU,GAAG,mBAAI,CAAC,IAAI,CAAC,qBAAS,CAAC,oBAAoB,EAAE,EAAE,SAAS,CAAC,CAAC;IAC1E,kBAAS,CAAC,IAAI,CAAC,aAAa,UAAU,kBAAkB,CAAC,CAAC;IAC1D,IAAI,UAAoB,CAAC;IAEzB,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,kBAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,yDAAyD;QACzD,UAAU,GAAG,EAAE,CAAC;IAClB,CAAC;IAED,MAAM,aAAa,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAChE,MAAM,KAAK,GAAqB,EAAE,CAAC;IAEnC,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC;QAC/B,MAAM,UAAU,GAAG,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACrD,kBAAS,CAAC,IAAI,CAAC,qBAAqB,UAAU,UAAU,UAAU,EAAE,CAAC,CAAC;QACtE,MAAM,UAAU,GAAG,MAAM,mBAAmB,CAAC,UAAU,
|
|
1
|
+
{"version":3,"file":"PluginLoader.js","sourceRoot":"","sources":["../../src/managers/PluginLoader.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,4DAyDC;AAhFD,gEAAkC;AAClC,0DAA6B;AAC7B,uCAAyC;AAEzC,kDAA+C;AAC/C,4CAA4C;AAE5C,uDAAyC;AAgBlC,KAAK,UAAU,wBAAwB;IAG5C,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;IAC9C,MAAM,kBAAkB,GAAuB;QAC7C,MAAM,EAAE,UAAU;QAClB,UAAU,EAAE,UAAU;KACvB,CAAC;IACF,MAAM,UAAU,GAAG,mBAAI,CAAC,IAAI,CAAC,qBAAS,CAAC,oBAAoB,EAAE,EAAE,SAAS,CAAC,CAAC;IAC1E,kBAAS,CAAC,IAAI,CAAC,aAAa,UAAU,kBAAkB,CAAC,CAAC;IAC1D,IAAI,UAAoB,CAAC;IAEzB,IAAI,CAAC;QACH,UAAU,GAAG,MAAM,kBAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,yDAAyD;QACzD,UAAU,GAAG,EAAE,CAAC;IAClB,CAAC;IAED,MAAM,aAAa,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;IAChE,MAAM,KAAK,GAAqB,EAAE,CAAC;IAEnC,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC;QAC/B,MAAM,UAAU,GAAG,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QACrD,kBAAS,CAAC,IAAI,CAAC,qBAAqB,UAAU,UAAU,UAAU,EAAE,CAAC,CAAC;QACtE,MAAM,UAAU,GAAG,MAAM,mBAAmB,CAAC,UAAU,CAAC,CAAC;QAEzD,IAAI,CAAC;YACH,MAAM,GAAG,GAAY,MAAM,MAAM,CAAC,IAAA,wBAAa,EAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC;YAElE,IACE,GAAG;gBACH,OAAQ,GAA6B,CAAC,eAAe,KAAK,UAAU,EACpE,CAAC;gBACD,MAAM,WAAW,GAAG,MAAO,GAAoB,CAAC,eAAe,CAC7D,kBAAkB,CACnB,CAAC;gBACF,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC;gBAC3B,kBAAS,CAAC,IAAI,CACZ,cAAc,WAAW,CAAC,MAAM,yBAAyB,UAAU,IAAI,CACxE,CAAC;gBACF,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;oBACxB,kBAAS,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;gBACnD,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,kBAAS,CAAC,KAAK,CACb,WAAW,UAAU,mCAAmC,mBAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,EACrF,GAAG,CACJ,CAAC;YACF,+CAA+C;QACjD,CAAC;IACH,CAAC;IAED,kBAAS,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,MAAM,+BAA+B,CAAC,CAAC;IACtE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,UAAkB;IACnD,OAAO,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AAC5C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "donobu",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.30.1",
|
|
4
4
|
"description": "Create browser automations with an LLM agent and replay them as Playwright scripts.",
|
|
5
5
|
"main": "dist/main.js",
|
|
6
6
|
"module": "dist/esm/main.js",
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
"types": "./dist/main.d.ts"
|
|
12
12
|
},
|
|
13
13
|
"bin": {
|
|
14
|
-
"playwright-json-to-markdown": "./dist/
|
|
15
|
-
"playwright-json-to-slack-json": "./dist/
|
|
14
|
+
"playwright-json-to-markdown": "./dist/cli/playwright-json-to-markdown.js",
|
|
15
|
+
"playwright-json-to-slack-json": "./dist/cli/playwright-json-to-slack-json.js",
|
|
16
|
+
"install-donobu-plugin": "./dist/cli/install-donobu-plugin.js"
|
|
16
17
|
},
|
|
17
18
|
"scripts": {
|
|
18
19
|
"clean": "rm -rf dist && rm -rf donobu-tool-plugins-for-docker",
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Convert Playwright JSON test results to a Markdown report.
|
|
5
|
-
*
|
|
6
|
-
* Usage: node playwright-json-to-markdown.js input.json > report.md
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
|
|
11
|
-
function stripAnsiCodes(str) {
|
|
12
|
-
return str.replace(
|
|
13
|
-
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
|
14
|
-
'',
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// Function to format duration in a readable way
|
|
19
|
-
function formatDuration(ms) {
|
|
20
|
-
if (ms < 1000) {
|
|
21
|
-
return `${ms}ms`;
|
|
22
|
-
}
|
|
23
|
-
const seconds = Math.floor(ms / 1000);
|
|
24
|
-
if (seconds < 60) {
|
|
25
|
-
return `${seconds}s`;
|
|
26
|
-
}
|
|
27
|
-
const minutes = Math.floor(seconds / 60);
|
|
28
|
-
const remainingSeconds = seconds % 60;
|
|
29
|
-
return `${minutes}m ${remainingSeconds}s`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Read the JSON data from file or stdin
|
|
33
|
-
function readInput() {
|
|
34
|
-
const args = process.argv.slice(2);
|
|
35
|
-
if (args.length > 0) {
|
|
36
|
-
return JSON.parse(fs.readFileSync(args[0], 'utf8'));
|
|
37
|
-
} else {
|
|
38
|
-
return JSON.parse(fs.readFileSync(0, 'utf8')); // Read from stdin
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Process JSON and create markdown
|
|
43
|
-
function generateMarkdown(jsonData) {
|
|
44
|
-
const { suites } = jsonData;
|
|
45
|
-
|
|
46
|
-
// Count self-healed tests
|
|
47
|
-
let selfHealedCount = 0;
|
|
48
|
-
suites.forEach((suite) => {
|
|
49
|
-
suite.specs.forEach((spec) => {
|
|
50
|
-
spec.tests.forEach((test) => {
|
|
51
|
-
if (
|
|
52
|
-
test.annotations &&
|
|
53
|
-
test.annotations.some((a) => a.type === 'self-healed')
|
|
54
|
-
) {
|
|
55
|
-
selfHealedCount++;
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// Create report header
|
|
62
|
-
let markdown = `# Playwright Test Report\n\n`;
|
|
63
|
-
// Tests by file
|
|
64
|
-
markdown += `## Summary\n\n`;
|
|
65
|
-
|
|
66
|
-
// Create file summary table with status counts
|
|
67
|
-
markdown += `| File | Passed | Self-Healed | Failed | Timed Out | Skipped | Interrupted | Duration |\n`;
|
|
68
|
-
markdown += `| - | - | - | - | - | - | - | - |\n`;
|
|
69
|
-
|
|
70
|
-
// Track totals for summary row
|
|
71
|
-
let totalPassed = 0;
|
|
72
|
-
let totalFailed = 0;
|
|
73
|
-
let totalTimedOut = 0;
|
|
74
|
-
let totalSkipped = 0;
|
|
75
|
-
let totalInterrupted = 0;
|
|
76
|
-
let totalSelfHealed = 0;
|
|
77
|
-
let totalDuration = 0;
|
|
78
|
-
|
|
79
|
-
suites.forEach((suite) => {
|
|
80
|
-
// Count tests by status for this file
|
|
81
|
-
let passed = 0;
|
|
82
|
-
let failed = 0;
|
|
83
|
-
let timedOut = 0;
|
|
84
|
-
let skipped = 0;
|
|
85
|
-
let interrupted = 0;
|
|
86
|
-
let selfHealed = 0;
|
|
87
|
-
|
|
88
|
-
const fileDuration = suite.specs.reduce(
|
|
89
|
-
(total, spec) =>
|
|
90
|
-
total +
|
|
91
|
-
spec.tests.reduce((testTotal, test) => {
|
|
92
|
-
const result = test.results && test.results.at(-1);
|
|
93
|
-
const isSelfHealed =
|
|
94
|
-
test.annotations &&
|
|
95
|
-
test.annotations.some((a) => a.type === 'self-healed');
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
test.status === 'skipped' ||
|
|
99
|
-
(!result && test.status === undefined)
|
|
100
|
-
) {
|
|
101
|
-
skipped++;
|
|
102
|
-
} else if (result) {
|
|
103
|
-
if (isSelfHealed) {
|
|
104
|
-
selfHealed++;
|
|
105
|
-
} else {
|
|
106
|
-
switch (result.status) {
|
|
107
|
-
case 'passed':
|
|
108
|
-
passed++;
|
|
109
|
-
break;
|
|
110
|
-
case 'failed':
|
|
111
|
-
failed++;
|
|
112
|
-
break;
|
|
113
|
-
case 'timedOut':
|
|
114
|
-
timedOut++;
|
|
115
|
-
break;
|
|
116
|
-
case 'skipped':
|
|
117
|
-
skipped++;
|
|
118
|
-
break;
|
|
119
|
-
case 'interrupted':
|
|
120
|
-
interrupted++;
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return testTotal + (result?.duration || 0);
|
|
127
|
-
}, 0),
|
|
128
|
-
0,
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
// Add to totals
|
|
132
|
-
totalPassed += passed;
|
|
133
|
-
totalFailed += failed;
|
|
134
|
-
totalTimedOut += timedOut;
|
|
135
|
-
totalSkipped += skipped;
|
|
136
|
-
totalInterrupted += interrupted;
|
|
137
|
-
totalSelfHealed += selfHealed;
|
|
138
|
-
totalDuration += fileDuration;
|
|
139
|
-
|
|
140
|
-
markdown += `| ${suite.file} | ${passed ? passed + ' ✅' : ''} | ${selfHealed ? selfHealed + ' ❤️🩹' : ''} | ${failed ? failed + ' ❌' : ''} | ${timedOut ? timedOut + ' ⏰' : ''} | ${skipped ? skipped + ' ⏭️' : ''} | ${interrupted ? interrupted + ' ⚡' : ''} | ${formatDuration(fileDuration)} |\n`;
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Add totals row
|
|
144
|
-
markdown += `| **TOTAL** | **${totalPassed + ' ✅'}** | **${totalSelfHealed + ' ❤️🩹'}** | **${totalFailed + ' ❌'}** | **${totalTimedOut + ' ⏰'}** | **${totalSkipped + ' ⏭️'}** | **${totalInterrupted + ' ⚡'}** | **${formatDuration(totalDuration)}** |\n`;
|
|
145
|
-
|
|
146
|
-
markdown += `\n`;
|
|
147
|
-
|
|
148
|
-
// Generate test details sections
|
|
149
|
-
suites.forEach((suite) => {
|
|
150
|
-
const fileName = suite.file;
|
|
151
|
-
markdown += `## ${fileName}\n\n`;
|
|
152
|
-
|
|
153
|
-
suite.specs.forEach((spec) => {
|
|
154
|
-
markdown += `### ${spec.title}\n\n`;
|
|
155
|
-
|
|
156
|
-
spec.tests.forEach((test) => {
|
|
157
|
-
const result = test.results && test.results.at(-1);
|
|
158
|
-
|
|
159
|
-
if (test.status === 'skipped' || !result || test.status === undefined) {
|
|
160
|
-
markdown += `**Status**: ⏭️ Skipped \n`;
|
|
161
|
-
markdown += `**Duration**: N/A \n`;
|
|
162
|
-
// Get objective from annotations if available
|
|
163
|
-
let objective = 'No objective provided';
|
|
164
|
-
|
|
165
|
-
if (test.annotations) {
|
|
166
|
-
const objectiveAnnotation = test.annotations.find(
|
|
167
|
-
(a) => a.type === 'objective',
|
|
168
|
-
);
|
|
169
|
-
if (objectiveAnnotation) {
|
|
170
|
-
objective =
|
|
171
|
-
objectiveAnnotation.description || 'No objective provided';
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Escape any existing triple backticks in the objective
|
|
176
|
-
objective = objective.replace(/```/g, '\\`\\`\\`');
|
|
177
|
-
markdown += `**Objective**:\n\`\`\`\n${objective}\n\`\`\`\n`;
|
|
178
|
-
markdown += `---\n\n`;
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const isSelfHealed =
|
|
183
|
-
test.annotations &&
|
|
184
|
-
test.annotations.some((a) => a.type === 'self-healed');
|
|
185
|
-
|
|
186
|
-
// Determine status based on result status and self-healing annotation
|
|
187
|
-
let status;
|
|
188
|
-
if (result.status === 'passed') {
|
|
189
|
-
status = '✅ Passed';
|
|
190
|
-
} else if (isSelfHealed) {
|
|
191
|
-
status = '❌ Failed (❤️🩹 Self-Healed)';
|
|
192
|
-
} else if (result.status === 'failed') {
|
|
193
|
-
status = '❌ Failed';
|
|
194
|
-
} else if (result.status === 'timedOut') {
|
|
195
|
-
status = '⏰ Timed Out';
|
|
196
|
-
} else if (result.status === 'skipped') {
|
|
197
|
-
status = '⏭️ Skipped';
|
|
198
|
-
} else if (result.status === 'interrupted') {
|
|
199
|
-
status = '⚡ Interrupted';
|
|
200
|
-
} else {
|
|
201
|
-
status = `⚠️ ${result.status || 'Unknown'}`;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const duration = formatDuration(result.duration || 0);
|
|
205
|
-
|
|
206
|
-
// Get objective from annotations if available
|
|
207
|
-
let objective = 'No objective provided';
|
|
208
|
-
if (test.annotations) {
|
|
209
|
-
const objectiveAnnotation = test.annotations.find(
|
|
210
|
-
(a) => a.type === 'objective',
|
|
211
|
-
);
|
|
212
|
-
if (objectiveAnnotation) {
|
|
213
|
-
objective =
|
|
214
|
-
objectiveAnnotation.description || 'No objective provided';
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Escape any existing triple backticks in the objective by replacing ``` with \`\`\`
|
|
219
|
-
objective = objective.replace(/```/g, '\\`\\`\\`');
|
|
220
|
-
|
|
221
|
-
markdown += `**Status**: ${status} \n`;
|
|
222
|
-
markdown += `**Duration**: ${duration} \n`;
|
|
223
|
-
markdown += `**Objective**:\n\`\`\`\n${objective}\n\`\`\`\n`;
|
|
224
|
-
|
|
225
|
-
// Add error details if test failed
|
|
226
|
-
if (result.status === 'failed' && result.error) {
|
|
227
|
-
markdown += `\n<details>\n<summary>⚠️ Error Details</summary>\n\n`;
|
|
228
|
-
markdown += `\`\`\`\n${result.error.message || 'No error message available'}\n\`\`\`\n\n`;
|
|
229
|
-
|
|
230
|
-
// Include code snippet if available
|
|
231
|
-
if (result.error.snippet) {
|
|
232
|
-
markdown += `**Code Snippet**:\n\`\`\`\n${stripAnsiCodes(result.error.snippet)}\n\`\`\`\n\n`;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
markdown += `</details>\n\n`;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
markdown += `---\n\n`;
|
|
239
|
-
});
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
// Add timestamp footer
|
|
244
|
-
markdown += `_Report generated on ${new Date().toLocaleString()} by Donobu_\n`;
|
|
245
|
-
|
|
246
|
-
return markdown;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Main execution
|
|
250
|
-
try {
|
|
251
|
-
const jsonData = readInput();
|
|
252
|
-
const markdown = generateMarkdown(jsonData);
|
|
253
|
-
console.log(markdown);
|
|
254
|
-
} catch (error) {
|
|
255
|
-
console.error('Error processing JSON:', error.message);
|
|
256
|
-
process.exit(1);
|
|
257
|
-
}
|