donobu 2.23.2 → 2.23.4
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1259
|
|
@@ -29,12 +29,6 @@ function formatDuration(ms) {
|
|
|
29
29
|
return `${minutes}m ${remainingSeconds}s`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
// Function to format date in a readable way
|
|
33
|
-
function formatDate(dateString) {
|
|
34
|
-
const date = new Date(dateString);
|
|
35
|
-
return date.toLocaleString();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
32
|
// Read the JSON data from file or stdin
|
|
39
33
|
function readInput() {
|
|
40
34
|
const args = process.argv.slice(2);
|
|
@@ -47,7 +41,7 @@ function readInput() {
|
|
|
47
41
|
|
|
48
42
|
// Process JSON and create markdown
|
|
49
43
|
function generateMarkdown(jsonData) {
|
|
50
|
-
const { suites
|
|
44
|
+
const { suites } = jsonData;
|
|
51
45
|
|
|
52
46
|
// Count self-healed tests
|
|
53
47
|
let selfHealedCount = 0;
|
|
@@ -66,117 +60,106 @@ function generateMarkdown(jsonData) {
|
|
|
66
60
|
|
|
67
61
|
// Create report header
|
|
68
62
|
let markdown = `# Playwright Test Report\n\n`;
|
|
69
|
-
|
|
70
|
-
// Summary section
|
|
63
|
+
// Tests by file
|
|
71
64
|
markdown += `## Summary\n\n`;
|
|
72
|
-
markdown += `- **Start Time**: ${formatDate(stats.startTime)}\n`;
|
|
73
|
-
markdown += `- **Duration**: ${formatDuration(stats.duration)}\n`;
|
|
74
|
-
markdown += `- **Tests**: ${stats.expected + stats.unexpected}\n`;
|
|
75
|
-
markdown += `- **Passed**: ${stats.expected}\n`;
|
|
76
|
-
markdown += `- **Self-Healed**: ${selfHealedCount}\n`;
|
|
77
|
-
markdown += `- **Failed**: ${stats.unexpected - selfHealedCount}\n`;
|
|
78
|
-
markdown += `- **Skipped**: ${stats.skipped}\n`;
|
|
79
|
-
markdown += `- **Flaky**: ${stats.flaky}\n\n`;
|
|
80
|
-
|
|
81
|
-
// Create pass rate and visual indicator
|
|
82
|
-
const totalTests = stats.expected + stats.unexpected;
|
|
83
|
-
const passRate =
|
|
84
|
-
totalTests > 0 ? Math.round((stats.expected / totalTests) * 100) : 0;
|
|
85
|
-
const selfHealedRate =
|
|
86
|
-
totalTests > 0 ? Math.round((selfHealedCount / totalTests) * 100) : 0;
|
|
87
|
-
const failRate =
|
|
88
|
-
totalTests > 0
|
|
89
|
-
? Math.round(((stats.unexpected - selfHealedCount) / totalTests) * 100)
|
|
90
|
-
: 0;
|
|
91
|
-
|
|
92
|
-
markdown += `### Test Results\n\n`;
|
|
93
|
-
markdown += `- **Passed**: ${passRate}%\n`;
|
|
94
|
-
markdown += `- **Self-Healed**: ${selfHealedRate}%\n`;
|
|
95
|
-
markdown += `- **Failed**: ${failRate}%\n\n`;
|
|
96
|
-
|
|
97
|
-
// Create visual pass/self-healed/fail bar
|
|
98
|
-
const passChar = '🟢';
|
|
99
|
-
const selfHealedChar = '❤️🩹';
|
|
100
|
-
const failChar = '🔴';
|
|
101
|
-
|
|
102
|
-
const passCount = stats.expected;
|
|
103
|
-
const failCount = stats.unexpected - selfHealedCount;
|
|
104
|
-
|
|
105
|
-
const statusBar =
|
|
106
|
-
passChar.repeat(passCount) +
|
|
107
|
-
selfHealedChar.repeat(selfHealedCount) +
|
|
108
|
-
failChar.repeat(failCount);
|
|
109
|
-
|
|
110
|
-
markdown += `${statusBar}\n\n`;
|
|
111
65
|
|
|
112
|
-
//
|
|
113
|
-
markdown +=
|
|
66
|
+
// Create file summary table with status counts
|
|
67
|
+
markdown += `| File | Passed | Self-Healed | Failed | Timed Out | Skipped | Interrupted | Duration |\n`;
|
|
68
|
+
markdown += `| - | - | - | - | - | - | - | - |\n`;
|
|
114
69
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
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;
|
|
118
78
|
|
|
119
79
|
suites.forEach((suite) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
+
|
|
124
88
|
const fileDuration = suite.specs.reduce(
|
|
125
89
|
(total, spec) =>
|
|
126
90
|
total +
|
|
127
91
|
spec.tests.reduce((testTotal, test) => {
|
|
128
|
-
const result = test.results && test.results
|
|
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 (result) {
|
|
98
|
+
if (isSelfHealed) {
|
|
99
|
+
selfHealed++;
|
|
100
|
+
} else {
|
|
101
|
+
switch (result.status) {
|
|
102
|
+
case 'passed':
|
|
103
|
+
passed++;
|
|
104
|
+
break;
|
|
105
|
+
case 'failed':
|
|
106
|
+
failed++;
|
|
107
|
+
break;
|
|
108
|
+
case 'timedOut':
|
|
109
|
+
timedOut++;
|
|
110
|
+
break;
|
|
111
|
+
case 'skipped':
|
|
112
|
+
skipped++;
|
|
113
|
+
break;
|
|
114
|
+
case 'interrupted':
|
|
115
|
+
interrupted++;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
129
121
|
return testTotal + (result?.duration || 0);
|
|
130
122
|
}, 0),
|
|
131
123
|
0,
|
|
132
124
|
);
|
|
133
125
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Determine status icon
|
|
150
|
-
let status;
|
|
151
|
-
if (allPassed) {
|
|
152
|
-
status = '✅';
|
|
153
|
-
} else if (hasSelfHealed) {
|
|
154
|
-
status = '❤️🩹';
|
|
155
|
-
} else {
|
|
156
|
-
status = '❌';
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
markdown += `| ${suite.file} | ${status} | ${fileTests} | ${formatDuration(fileDuration)} |\n`;
|
|
126
|
+
// Add to totals
|
|
127
|
+
totalPassed += passed;
|
|
128
|
+
totalFailed += failed;
|
|
129
|
+
totalTimedOut += timedOut;
|
|
130
|
+
totalSkipped += skipped;
|
|
131
|
+
totalInterrupted += interrupted;
|
|
132
|
+
totalSelfHealed += selfHealed;
|
|
133
|
+
totalDuration += fileDuration;
|
|
134
|
+
const anchor = suite.file
|
|
135
|
+
.replace(/[^\w\s-]/g, '') // remove non-alphanumeric
|
|
136
|
+
.trim()
|
|
137
|
+
.replace(/\s+/g, '-') // spaces to dashes
|
|
138
|
+
.toLowerCase();
|
|
139
|
+
|
|
140
|
+
markdown += `| [${suite.file}](#${anchor}) | ${passed ? passed + ' ✅' : ''} | ${selfHealed ? selfHealed + ' ❤️🩹' : ''} | ${failed ? failed + ' ❌' : ''} | ${timedOut ? timedOut + ' ⏰' : ''} | ${skipped ? skipped + ' ⏭️' : ''} | ${interrupted ? interrupted + ' ⚡' : ''} | ${formatDuration(fileDuration)} |\n`;
|
|
160
141
|
});
|
|
161
142
|
|
|
143
|
+
// Add totals row
|
|
144
|
+
markdown += `| **TOTAL** | **${totalPassed + ' ✅'}** | **${totalSelfHealed + ' ❤️🩹'}** | **${totalFailed + ' ❌'}** | **${totalTimedOut + ' ⏰'}** | **${totalSkipped + ' ⏭️'}** | **${totalInterrupted + ' ⚡'}** | **${formatDuration(totalDuration)}** |\n`;
|
|
145
|
+
|
|
162
146
|
markdown += `\n`;
|
|
163
147
|
|
|
164
148
|
// Generate test details sections
|
|
165
149
|
suites.forEach((suite) => {
|
|
166
|
-
const fileName = suite.file
|
|
150
|
+
const fileName = suite.file;
|
|
167
151
|
markdown += `## ${fileName}\n\n`;
|
|
168
152
|
|
|
169
153
|
suite.specs.forEach((spec) => {
|
|
170
154
|
markdown += `### ${spec.title}\n\n`;
|
|
171
155
|
|
|
172
156
|
spec.tests.forEach((test) => {
|
|
173
|
-
|
|
174
|
-
const result = test.results && test.results[0];
|
|
157
|
+
const result = test.results && test.results.at(-1);
|
|
175
158
|
|
|
176
159
|
// Skip tests without results
|
|
177
160
|
if (!result) {
|
|
178
|
-
markdown += `**Status**: ⚠️ No Results
|
|
179
|
-
markdown += `**Duration**: N/A
|
|
161
|
+
markdown += `**Status**: ⚠️ No Results\n`;
|
|
162
|
+
markdown += `**Duration**: N/A\n`;
|
|
180
163
|
markdown += `**Objective**: Test skipped or no results available\n`;
|
|
181
164
|
markdown += `---\n\n`;
|
|
182
165
|
return;
|
|
@@ -194,8 +177,12 @@ function generateMarkdown(jsonData) {
|
|
|
194
177
|
status = '❌ Failed (❤️🩹 Self-Healed)';
|
|
195
178
|
} else if (result.status === 'failed') {
|
|
196
179
|
status = '❌ Failed';
|
|
180
|
+
} else if (result.status === 'timedOut') {
|
|
181
|
+
status = '⏰ Timed Out';
|
|
197
182
|
} else if (result.status === 'skipped') {
|
|
198
183
|
status = '⏭️ Skipped';
|
|
184
|
+
} else if (result.status === 'interrupted') {
|
|
185
|
+
status = '⚡ Interrupted';
|
|
199
186
|
} else {
|
|
200
187
|
status = `⚠️ ${result.status || 'Unknown'}`;
|
|
201
188
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1259
|
|
@@ -29,12 +29,6 @@ function formatDuration(ms) {
|
|
|
29
29
|
return `${minutes}m ${remainingSeconds}s`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
// Function to format date in a readable way
|
|
33
|
-
function formatDate(dateString) {
|
|
34
|
-
const date = new Date(dateString);
|
|
35
|
-
return date.toLocaleString();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
32
|
// Read the JSON data from file or stdin
|
|
39
33
|
function readInput() {
|
|
40
34
|
const args = process.argv.slice(2);
|
|
@@ -47,7 +41,7 @@ function readInput() {
|
|
|
47
41
|
|
|
48
42
|
// Process JSON and create markdown
|
|
49
43
|
function generateMarkdown(jsonData) {
|
|
50
|
-
const { suites
|
|
44
|
+
const { suites } = jsonData;
|
|
51
45
|
|
|
52
46
|
// Count self-healed tests
|
|
53
47
|
let selfHealedCount = 0;
|
|
@@ -66,117 +60,106 @@ function generateMarkdown(jsonData) {
|
|
|
66
60
|
|
|
67
61
|
// Create report header
|
|
68
62
|
let markdown = `# Playwright Test Report\n\n`;
|
|
69
|
-
|
|
70
|
-
// Summary section
|
|
63
|
+
// Tests by file
|
|
71
64
|
markdown += `## Summary\n\n`;
|
|
72
|
-
markdown += `- **Start Time**: ${formatDate(stats.startTime)}\n`;
|
|
73
|
-
markdown += `- **Duration**: ${formatDuration(stats.duration)}\n`;
|
|
74
|
-
markdown += `- **Tests**: ${stats.expected + stats.unexpected}\n`;
|
|
75
|
-
markdown += `- **Passed**: ${stats.expected}\n`;
|
|
76
|
-
markdown += `- **Self-Healed**: ${selfHealedCount}\n`;
|
|
77
|
-
markdown += `- **Failed**: ${stats.unexpected - selfHealedCount}\n`;
|
|
78
|
-
markdown += `- **Skipped**: ${stats.skipped}\n`;
|
|
79
|
-
markdown += `- **Flaky**: ${stats.flaky}\n\n`;
|
|
80
|
-
|
|
81
|
-
// Create pass rate and visual indicator
|
|
82
|
-
const totalTests = stats.expected + stats.unexpected;
|
|
83
|
-
const passRate =
|
|
84
|
-
totalTests > 0 ? Math.round((stats.expected / totalTests) * 100) : 0;
|
|
85
|
-
const selfHealedRate =
|
|
86
|
-
totalTests > 0 ? Math.round((selfHealedCount / totalTests) * 100) : 0;
|
|
87
|
-
const failRate =
|
|
88
|
-
totalTests > 0
|
|
89
|
-
? Math.round(((stats.unexpected - selfHealedCount) / totalTests) * 100)
|
|
90
|
-
: 0;
|
|
91
|
-
|
|
92
|
-
markdown += `### Test Results\n\n`;
|
|
93
|
-
markdown += `- **Passed**: ${passRate}%\n`;
|
|
94
|
-
markdown += `- **Self-Healed**: ${selfHealedRate}%\n`;
|
|
95
|
-
markdown += `- **Failed**: ${failRate}%\n\n`;
|
|
96
|
-
|
|
97
|
-
// Create visual pass/self-healed/fail bar
|
|
98
|
-
const passChar = '🟢';
|
|
99
|
-
const selfHealedChar = '❤️🩹';
|
|
100
|
-
const failChar = '🔴';
|
|
101
|
-
|
|
102
|
-
const passCount = stats.expected;
|
|
103
|
-
const failCount = stats.unexpected - selfHealedCount;
|
|
104
|
-
|
|
105
|
-
const statusBar =
|
|
106
|
-
passChar.repeat(passCount) +
|
|
107
|
-
selfHealedChar.repeat(selfHealedCount) +
|
|
108
|
-
failChar.repeat(failCount);
|
|
109
|
-
|
|
110
|
-
markdown += `${statusBar}\n\n`;
|
|
111
65
|
|
|
112
|
-
//
|
|
113
|
-
markdown +=
|
|
66
|
+
// Create file summary table with status counts
|
|
67
|
+
markdown += `| File | Passed | Self-Healed | Failed | Timed Out | Skipped | Interrupted | Duration |\n`;
|
|
68
|
+
markdown += `| - | - | - | - | - | - | - | - |\n`;
|
|
114
69
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
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;
|
|
118
78
|
|
|
119
79
|
suites.forEach((suite) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
+
|
|
124
88
|
const fileDuration = suite.specs.reduce(
|
|
125
89
|
(total, spec) =>
|
|
126
90
|
total +
|
|
127
91
|
spec.tests.reduce((testTotal, test) => {
|
|
128
|
-
const result = test.results && test.results
|
|
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 (result) {
|
|
98
|
+
if (isSelfHealed) {
|
|
99
|
+
selfHealed++;
|
|
100
|
+
} else {
|
|
101
|
+
switch (result.status) {
|
|
102
|
+
case 'passed':
|
|
103
|
+
passed++;
|
|
104
|
+
break;
|
|
105
|
+
case 'failed':
|
|
106
|
+
failed++;
|
|
107
|
+
break;
|
|
108
|
+
case 'timedOut':
|
|
109
|
+
timedOut++;
|
|
110
|
+
break;
|
|
111
|
+
case 'skipped':
|
|
112
|
+
skipped++;
|
|
113
|
+
break;
|
|
114
|
+
case 'interrupted':
|
|
115
|
+
interrupted++;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
129
121
|
return testTotal + (result?.duration || 0);
|
|
130
122
|
}, 0),
|
|
131
123
|
0,
|
|
132
124
|
);
|
|
133
125
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
// Determine status icon
|
|
150
|
-
let status;
|
|
151
|
-
if (allPassed) {
|
|
152
|
-
status = '✅';
|
|
153
|
-
} else if (hasSelfHealed) {
|
|
154
|
-
status = '❤️🩹';
|
|
155
|
-
} else {
|
|
156
|
-
status = '❌';
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
markdown += `| ${suite.file} | ${status} | ${fileTests} | ${formatDuration(fileDuration)} |\n`;
|
|
126
|
+
// Add to totals
|
|
127
|
+
totalPassed += passed;
|
|
128
|
+
totalFailed += failed;
|
|
129
|
+
totalTimedOut += timedOut;
|
|
130
|
+
totalSkipped += skipped;
|
|
131
|
+
totalInterrupted += interrupted;
|
|
132
|
+
totalSelfHealed += selfHealed;
|
|
133
|
+
totalDuration += fileDuration;
|
|
134
|
+
const anchor = suite.file
|
|
135
|
+
.replace(/[^\w\s-]/g, '') // remove non-alphanumeric
|
|
136
|
+
.trim()
|
|
137
|
+
.replace(/\s+/g, '-') // spaces to dashes
|
|
138
|
+
.toLowerCase();
|
|
139
|
+
|
|
140
|
+
markdown += `| [${suite.file}](#${anchor}) | ${passed ? passed + ' ✅' : ''} | ${selfHealed ? selfHealed + ' ❤️🩹' : ''} | ${failed ? failed + ' ❌' : ''} | ${timedOut ? timedOut + ' ⏰' : ''} | ${skipped ? skipped + ' ⏭️' : ''} | ${interrupted ? interrupted + ' ⚡' : ''} | ${formatDuration(fileDuration)} |\n`;
|
|
160
141
|
});
|
|
161
142
|
|
|
143
|
+
// Add totals row
|
|
144
|
+
markdown += `| **TOTAL** | **${totalPassed + ' ✅'}** | **${totalSelfHealed + ' ❤️🩹'}** | **${totalFailed + ' ❌'}** | **${totalTimedOut + ' ⏰'}** | **${totalSkipped + ' ⏭️'}** | **${totalInterrupted + ' ⚡'}** | **${formatDuration(totalDuration)}** |\n`;
|
|
145
|
+
|
|
162
146
|
markdown += `\n`;
|
|
163
147
|
|
|
164
148
|
// Generate test details sections
|
|
165
149
|
suites.forEach((suite) => {
|
|
166
|
-
const fileName = suite.file
|
|
150
|
+
const fileName = suite.file;
|
|
167
151
|
markdown += `## ${fileName}\n\n`;
|
|
168
152
|
|
|
169
153
|
suite.specs.forEach((spec) => {
|
|
170
154
|
markdown += `### ${spec.title}\n\n`;
|
|
171
155
|
|
|
172
156
|
spec.tests.forEach((test) => {
|
|
173
|
-
|
|
174
|
-
const result = test.results && test.results[0];
|
|
157
|
+
const result = test.results && test.results.at(-1);
|
|
175
158
|
|
|
176
159
|
// Skip tests without results
|
|
177
160
|
if (!result) {
|
|
178
|
-
markdown += `**Status**: ⚠️ No Results
|
|
179
|
-
markdown += `**Duration**: N/A
|
|
161
|
+
markdown += `**Status**: ⚠️ No Results\n`;
|
|
162
|
+
markdown += `**Duration**: N/A\n`;
|
|
180
163
|
markdown += `**Objective**: Test skipped or no results available\n`;
|
|
181
164
|
markdown += `---\n\n`;
|
|
182
165
|
return;
|
|
@@ -194,8 +177,12 @@ function generateMarkdown(jsonData) {
|
|
|
194
177
|
status = '❌ Failed (❤️🩹 Self-Healed)';
|
|
195
178
|
} else if (result.status === 'failed') {
|
|
196
179
|
status = '❌ Failed';
|
|
180
|
+
} else if (result.status === 'timedOut') {
|
|
181
|
+
status = '⏰ Timed Out';
|
|
197
182
|
} else if (result.status === 'skipped') {
|
|
198
183
|
status = '⏭️ Skipped';
|
|
184
|
+
} else if (result.status === 'interrupted') {
|
|
185
|
+
status = '⚡ Interrupted';
|
|
199
186
|
} else {
|
|
200
187
|
status = `⚠️ ${result.status || 'Unknown'}`;
|
|
201
188
|
}
|