playwright-slack-report 1.0.13 → 1.0.18
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/README.md +9 -0
- package/dist/src/LayoutGenerator.d.ts +1 -1
- package/dist/src/LayoutGenerator.js +2 -3
- package/dist/src/ResultsParser.d.ts +4 -1
- package/dist/src/ResultsParser.js +36 -10
- package/dist/src/SlackClient.d.ts +1 -0
- package/dist/src/SlackClient.js +4 -1
- package/dist/src/SlackReporter.d.ts +1 -0
- package/dist/src/SlackReporter.js +7 -2
- package/dist/src/index.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://gitpod.io/#https://github.com/ryanrosello-og/playwright-slack-report)
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
Publish your Playwright test results to your favorite Slack channel(s).
|
|
6
8
|
|
|
7
9
|

|
|
@@ -102,6 +104,7 @@ An example advanced configuration is shown below:
|
|
|
102
104
|
sendResults: "always", // "always" , "on-failure", "off"
|
|
103
105
|
},
|
|
104
106
|
layout: generateCustomLayout,
|
|
107
|
+
maxNumberOfFailuresToShow: 4,
|
|
105
108
|
meta: [
|
|
106
109
|
{
|
|
107
110
|
key: 'BUILD_NUMBER',
|
|
@@ -111,6 +114,10 @@ An example advanced configuration is shown below:
|
|
|
111
114
|
key: 'WHATEVER_ENV_VARIABLE',
|
|
112
115
|
value: process.env.SOME_ENV_VARIABLE, // depending on your CI environment, this can be the branch name, build id, etc
|
|
113
116
|
},
|
|
117
|
+
{
|
|
118
|
+
key: 'HTML Results',
|
|
119
|
+
value: '<https://your-build-artifacts.my.company.dev/pw/23887/playwright-report/index.html|📊>',
|
|
120
|
+
},
|
|
114
121
|
],
|
|
115
122
|
],
|
|
116
123
|
],
|
|
@@ -126,6 +133,8 @@ Can either be *"always"*, *"on-failure"* or *"off"*, this configuration is requi
|
|
|
126
133
|
### **layout**
|
|
127
134
|
A function that returns a layout object, this configuration is optional. See section below for more details.
|
|
128
135
|
* meta - an array of meta data to be sent to Slack, this configuration is optional.
|
|
136
|
+
### **maxNumberOfFailuresToShow**
|
|
137
|
+
Limits the number of failures shown in the Slack message, defaults to 10.
|
|
129
138
|
|
|
130
139
|
**Examples:**
|
|
131
140
|
```typescript
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { KnownBlock, Block } from '@slack/types';
|
|
2
2
|
import { SummaryResults } from '.';
|
|
3
|
-
declare const generateBlocks: (summaryResults: SummaryResults) => Promise<Array<KnownBlock | Block>>;
|
|
3
|
+
declare const generateBlocks: (summaryResults: SummaryResults, maxNumberOfFailures: number) => Promise<Array<KnownBlock | Block>>;
|
|
4
4
|
export default generateBlocks;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const generateBlocks = async (summaryResults) => {
|
|
4
|
-
const maxNumberOfFailures = 10;
|
|
3
|
+
const generateBlocks = async (summaryResults, maxNumberOfFailures) => {
|
|
5
4
|
const maxNumberOfFailureLength = 650;
|
|
6
5
|
const fails = [];
|
|
7
6
|
const meta = [];
|
|
@@ -39,7 +38,7 @@ const generateBlocks = async (summaryResults) => {
|
|
|
39
38
|
type: 'section',
|
|
40
39
|
text: {
|
|
41
40
|
type: 'mrkdwn',
|
|
42
|
-
text:
|
|
41
|
+
text: `*There are too many failures to display - ${fails.length} out of ${summaryResults.failures.length} failures shown*`,
|
|
43
42
|
},
|
|
44
43
|
});
|
|
45
44
|
break;
|
|
@@ -4,9 +4,11 @@ export declare type testResult = {
|
|
|
4
4
|
suiteName: string;
|
|
5
5
|
name: string;
|
|
6
6
|
browser?: string;
|
|
7
|
+
projectName: string;
|
|
7
8
|
endedAt: string;
|
|
8
9
|
reason: string;
|
|
9
10
|
retry: number;
|
|
11
|
+
retries: number;
|
|
10
12
|
startedAt: string;
|
|
11
13
|
status: 'passed' | 'failed' | 'timedOut' | 'skipped';
|
|
12
14
|
attachments?: {
|
|
@@ -28,10 +30,11 @@ export default class ResultsParser {
|
|
|
28
30
|
constructor();
|
|
29
31
|
getParsedResults(): Promise<SummaryResults>;
|
|
30
32
|
getFailures(): Promise<Array<failure>>;
|
|
33
|
+
static getTestName(failedTest: any): any;
|
|
31
34
|
updateResults(data: {
|
|
32
35
|
testSuite: any;
|
|
33
36
|
}): void;
|
|
34
|
-
addTestResult(suiteName: any,
|
|
37
|
+
addTestResult(suiteName: any, testCase: any): void;
|
|
35
38
|
safelyDetermineFailure(result: {
|
|
36
39
|
errors: any[];
|
|
37
40
|
error: {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/* eslint-disable no-shadow */
|
|
3
|
+
/* eslint-disable no-underscore-dangle */
|
|
2
4
|
/* eslint-disable import/extensions */
|
|
3
5
|
/* eslint-disable no-control-regex */
|
|
4
6
|
/* eslint-disable class-methods-use-this */
|
|
@@ -36,12 +38,10 @@ class ResultsParser {
|
|
|
36
38
|
for (const suite of this.result) {
|
|
37
39
|
for (const test of suite.testSuite.tests) {
|
|
38
40
|
if (test.status === 'failed' || test.status === 'timedOut') {
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
&& f.failureReason === test.reason);
|
|
42
|
-
if (!failureExists) {
|
|
41
|
+
// only flag as failed if the last attempt has failed
|
|
42
|
+
if (test.retries === test.retry) {
|
|
43
43
|
failures.push({
|
|
44
|
-
test: test
|
|
44
|
+
test: ResultsParser.getTestName(test),
|
|
45
45
|
failureReason: test.reason,
|
|
46
46
|
});
|
|
47
47
|
}
|
|
@@ -50,19 +50,38 @@ class ResultsParser {
|
|
|
50
50
|
}
|
|
51
51
|
return failures;
|
|
52
52
|
}
|
|
53
|
+
static getTestName(failedTest) {
|
|
54
|
+
const testName = failedTest.name;
|
|
55
|
+
if (failedTest.browser && failedTest.projectName) {
|
|
56
|
+
if (failedTest.browser === failedTest.projectName) {
|
|
57
|
+
return `${testName} [${failedTest.browser}]`;
|
|
58
|
+
}
|
|
59
|
+
return `${testName} [Project Name: ${failedTest.projectName}] using ${failedTest.browser}`;
|
|
60
|
+
}
|
|
61
|
+
return testName;
|
|
62
|
+
}
|
|
53
63
|
updateResults(data) {
|
|
54
64
|
if (data.testSuite.tests.length > 0) {
|
|
55
65
|
this.result.push(data);
|
|
56
66
|
}
|
|
57
67
|
}
|
|
58
|
-
addTestResult(suiteName,
|
|
68
|
+
addTestResult(suiteName, testCase) {
|
|
59
69
|
const testResults = [];
|
|
60
|
-
for (const result of
|
|
70
|
+
for (const result of testCase.results) {
|
|
61
71
|
testResults.push({
|
|
62
72
|
suiteName,
|
|
63
|
-
name:
|
|
73
|
+
name: testCase.title,
|
|
64
74
|
status: result.status,
|
|
75
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
76
|
+
browser: testCase.parent?.parent?._projectConfig?.use?.defaultBrowserType
|
|
77
|
+
? testCase.parent.parent._projectConfig.use.defaultBrowserType
|
|
78
|
+
: '',
|
|
79
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
80
|
+
projectName: testCase.parent?.parent?._projectConfig?.name
|
|
81
|
+
? testCase.parent.parent._projectConfig.name
|
|
82
|
+
: '',
|
|
65
83
|
retry: result.retry,
|
|
84
|
+
retries: testCase.retries,
|
|
66
85
|
startedAt: new Date(result.startTime).toISOString(),
|
|
67
86
|
endedAt: new Date(new Date(result.startTime).getTime() + result.duration).toISOString(),
|
|
68
87
|
reason: this.safelyDetermineFailure(result),
|
|
@@ -78,7 +97,9 @@ class ResultsParser {
|
|
|
78
97
|
}
|
|
79
98
|
safelyDetermineFailure(result) {
|
|
80
99
|
if (result.errors.length > 0) {
|
|
81
|
-
const fullError = result.errors
|
|
100
|
+
const fullError = result.errors
|
|
101
|
+
.map((e) => `${e.message}\r\n${e.stack ? e.stack : ''}\r\n`)
|
|
102
|
+
.join();
|
|
82
103
|
return this.cleanseReason(fullError);
|
|
83
104
|
}
|
|
84
105
|
return `${this.cleanseReason(result.error?.message)} \n ${this.cleanseReason(result.error?.stack)}`;
|
|
@@ -86,7 +107,12 @@ class ResultsParser {
|
|
|
86
107
|
cleanseReason(rawReaseon) {
|
|
87
108
|
// eslint-disable-next-line prefer-regex-literals
|
|
88
109
|
const ansiRegex = new RegExp('([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))', 'g');
|
|
89
|
-
|
|
110
|
+
const ansiCleansed = rawReaseon ? rawReaseon.replace(ansiRegex, '') : '';
|
|
111
|
+
const logsStripped = ansiCleansed
|
|
112
|
+
.replace(/============================================================\n/g, '')
|
|
113
|
+
.replace(/============================================================\r\n/g, '')
|
|
114
|
+
.replace(/=========================== logs ===========================\n/g, '');
|
|
115
|
+
return logsStripped;
|
|
90
116
|
}
|
|
91
117
|
}
|
|
92
118
|
exports.default = ResultsParser;
|
package/dist/src/SlackClient.js
CHANGED
|
@@ -12,7 +12,7 @@ class SlackClient {
|
|
|
12
12
|
blocks = options.customLayout(options.summaryResults);
|
|
13
13
|
}
|
|
14
14
|
else {
|
|
15
|
-
blocks = await (0, LayoutGenerator_1.default)(options.summaryResults);
|
|
15
|
+
blocks = await (0, LayoutGenerator_1.default)(options.summaryResults, options.maxNumberOfFailures);
|
|
16
16
|
}
|
|
17
17
|
if (!options.channelIds) {
|
|
18
18
|
throw new Error(`Channel ids [${options.channelIds}] is not valid`);
|
|
@@ -34,6 +34,9 @@ class SlackClient {
|
|
|
34
34
|
// eslint-disable-next-line no-console
|
|
35
35
|
console.log(`✅ Message sent to ${channel}`);
|
|
36
36
|
}
|
|
37
|
+
else {
|
|
38
|
+
result.push({ channel, outcome: `❌ Message not sent to ${channel} \r\n ${JSON.stringify(chatResponse, null, 2)}` });
|
|
39
|
+
}
|
|
37
40
|
}
|
|
38
41
|
catch (error) {
|
|
39
42
|
result.push({
|
|
@@ -9,6 +9,7 @@ class SlackReporter {
|
|
|
9
9
|
slackChannels = [];
|
|
10
10
|
meta = [];
|
|
11
11
|
customLayout;
|
|
12
|
+
maxNumberOfFailuresToShow;
|
|
12
13
|
resultsParser;
|
|
13
14
|
logs = [];
|
|
14
15
|
onBegin(fullConfig, suite) {
|
|
@@ -20,6 +21,7 @@ class SlackReporter {
|
|
|
20
21
|
this.sendResults = slackReporterConfig.sendResults || 'always';
|
|
21
22
|
this.customLayout = slackReporterConfig.layout;
|
|
22
23
|
this.slackChannels = slackReporterConfig.channels;
|
|
24
|
+
this.maxNumberOfFailuresToShow = slackReporterConfig.maxNumberOfFailuresToShow || 10;
|
|
23
25
|
}
|
|
24
26
|
this.resultsParser = new ResultsParser_1.default();
|
|
25
27
|
}
|
|
@@ -37,20 +39,23 @@ class SlackReporter {
|
|
|
37
39
|
resultSummary.meta = this.meta;
|
|
38
40
|
const maxRetry = Math.max(...resultSummary.tests.map((o) => o.retry));
|
|
39
41
|
if (this.sendResults === 'on-failure'
|
|
40
|
-
&& resultSummary.tests.filter((z) => z.status === 'failed' && z.retry === maxRetry).length === 0) {
|
|
42
|
+
&& resultSummary.tests.filter((z) => (z.status === 'failed' || z.status === 'timedOut') && z.retry === maxRetry).length === 0) {
|
|
41
43
|
this.log('⏩ Slack reporter - no failures found');
|
|
42
44
|
return;
|
|
43
45
|
}
|
|
44
46
|
const slackClient = new SlackClient_1.default(new web_api_1.WebClient(process.env.SLACK_BOT_USER_OAUTH_TOKEN, {
|
|
45
47
|
logLevel: web_api_1.LogLevel.DEBUG,
|
|
46
48
|
}));
|
|
47
|
-
await slackClient.sendMessage({
|
|
49
|
+
const result = await slackClient.sendMessage({
|
|
48
50
|
options: {
|
|
49
51
|
channelIds: this.slackChannels,
|
|
50
52
|
summaryResults: resultSummary,
|
|
51
53
|
customLayout: this.customLayout,
|
|
54
|
+
maxNumberOfFailures: this.maxNumberOfFailuresToShow,
|
|
52
55
|
},
|
|
53
56
|
});
|
|
57
|
+
// eslint-disable-next-line no-console
|
|
58
|
+
console.log(JSON.stringify(result, null, 2));
|
|
54
59
|
}
|
|
55
60
|
preChecks() {
|
|
56
61
|
if (this.sendResults === 'off') {
|
package/dist/src/index.d.ts
CHANGED
package/package.json
CHANGED