playwright-slack-report 1.0.19 → 1.0.20
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 +210 -11
- package/dist/src/LayoutGenerator.d.ts +4 -4
- package/dist/src/LayoutGenerator.js +68 -68
- package/dist/src/ResultsParser.d.ts +46 -46
- package/dist/src/ResultsParser.js +118 -118
- package/dist/src/SlackClient.d.ts +24 -23
- package/dist/src/SlackClient.js +62 -59
- package/dist/src/SlackReporter.d.ts +21 -20
- package/dist/src/SlackReporter.js +112 -102
- package/dist/src/custom_block/my_block.d.ts +4 -4
- package/dist/src/custom_block/my_block.js +86 -86
- package/dist/src/custom_block/simple.d.ts +3 -3
- package/dist/src/custom_block/simple.js +16 -16
- package/dist/src/custom_block/simple_with_meta.d.ts +3 -3
- package/dist/src/custom_block/simple_with_meta.js +30 -30
- package/dist/src/index.d.ts +32 -32
- package/dist/src/index.js +2 -2
- package/package.json +1 -1
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/* eslint-disable no-shadow */
|
|
3
|
-
/* eslint-disable no-underscore-dangle */
|
|
4
|
-
/* eslint-disable import/extensions */
|
|
5
|
-
/* eslint-disable no-control-regex */
|
|
6
|
-
/* eslint-disable class-methods-use-this */
|
|
7
|
-
/* eslint-disable no-param-reassign */
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
class ResultsParser {
|
|
10
|
-
result;
|
|
11
|
-
constructor() {
|
|
12
|
-
this.result = [];
|
|
13
|
-
}
|
|
14
|
-
async getParsedResults() {
|
|
15
|
-
const failures = await this.getFailures();
|
|
16
|
-
const summary = {
|
|
17
|
-
passed: 0,
|
|
18
|
-
failed: failures.length,
|
|
19
|
-
skipped: 0,
|
|
20
|
-
failures,
|
|
21
|
-
tests: [],
|
|
22
|
-
};
|
|
23
|
-
for (const suite of this.result) {
|
|
24
|
-
summary.tests = summary.tests.concat(suite.testSuite.tests);
|
|
25
|
-
for (const test of suite.testSuite.tests) {
|
|
26
|
-
if (test.status === 'passed') {
|
|
27
|
-
summary.passed += 1;
|
|
28
|
-
}
|
|
29
|
-
else if (test.status === 'skipped') {
|
|
30
|
-
summary.skipped += 1;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return summary;
|
|
35
|
-
}
|
|
36
|
-
async getFailures() {
|
|
37
|
-
const failures = [];
|
|
38
|
-
for (const suite of this.result) {
|
|
39
|
-
for (const test of suite.testSuite.tests) {
|
|
40
|
-
if (test.status === 'failed' || test.status === 'timedOut') {
|
|
41
|
-
// only flag as failed if the last attempt has failed
|
|
42
|
-
if (test.retries === test.retry) {
|
|
43
|
-
failures.push({
|
|
44
|
-
test: ResultsParser.getTestName(test),
|
|
45
|
-
failureReason: test.reason,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return failures;
|
|
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
|
-
}
|
|
63
|
-
updateResults(data) {
|
|
64
|
-
if (data.testSuite.tests.length > 0) {
|
|
65
|
-
this.result.push(data);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
addTestResult(suiteName, testCase) {
|
|
69
|
-
const testResults = [];
|
|
70
|
-
for (const result of testCase.results) {
|
|
71
|
-
testResults.push({
|
|
72
|
-
suiteName,
|
|
73
|
-
name: testCase.title,
|
|
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
|
-
: '',
|
|
83
|
-
retry: result.retry,
|
|
84
|
-
retries: testCase.retries,
|
|
85
|
-
startedAt: new Date(result.startTime).toISOString(),
|
|
86
|
-
endedAt: new Date(new Date(result.startTime).getTime() + result.duration).toISOString(),
|
|
87
|
-
reason: this.safelyDetermineFailure(result),
|
|
88
|
-
attachments: result.attachments,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
this.updateResults({
|
|
92
|
-
testSuite: {
|
|
93
|
-
title: suiteName,
|
|
94
|
-
tests: testResults,
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
safelyDetermineFailure(result) {
|
|
99
|
-
if (result.errors.length > 0) {
|
|
100
|
-
const fullError = result.errors
|
|
101
|
-
.map((e) => `${e.message}\r\n${e.stack ? e.stack : ''}\r\n`)
|
|
102
|
-
.join();
|
|
103
|
-
return this.cleanseReason(fullError);
|
|
104
|
-
}
|
|
105
|
-
return `${this.cleanseReason(result.error?.message)} \n ${this.cleanseReason(result.error?.stack)}`;
|
|
106
|
-
}
|
|
107
|
-
cleanseReason(rawReaseon) {
|
|
108
|
-
// eslint-disable-next-line prefer-regex-literals
|
|
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');
|
|
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;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
exports.default = ResultsParser;
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable no-shadow */
|
|
3
|
+
/* eslint-disable no-underscore-dangle */
|
|
4
|
+
/* eslint-disable import/extensions */
|
|
5
|
+
/* eslint-disable no-control-regex */
|
|
6
|
+
/* eslint-disable class-methods-use-this */
|
|
7
|
+
/* eslint-disable no-param-reassign */
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
class ResultsParser {
|
|
10
|
+
result;
|
|
11
|
+
constructor() {
|
|
12
|
+
this.result = [];
|
|
13
|
+
}
|
|
14
|
+
async getParsedResults() {
|
|
15
|
+
const failures = await this.getFailures();
|
|
16
|
+
const summary = {
|
|
17
|
+
passed: 0,
|
|
18
|
+
failed: failures.length,
|
|
19
|
+
skipped: 0,
|
|
20
|
+
failures,
|
|
21
|
+
tests: [],
|
|
22
|
+
};
|
|
23
|
+
for (const suite of this.result) {
|
|
24
|
+
summary.tests = summary.tests.concat(suite.testSuite.tests);
|
|
25
|
+
for (const test of suite.testSuite.tests) {
|
|
26
|
+
if (test.status === 'passed') {
|
|
27
|
+
summary.passed += 1;
|
|
28
|
+
}
|
|
29
|
+
else if (test.status === 'skipped') {
|
|
30
|
+
summary.skipped += 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return summary;
|
|
35
|
+
}
|
|
36
|
+
async getFailures() {
|
|
37
|
+
const failures = [];
|
|
38
|
+
for (const suite of this.result) {
|
|
39
|
+
for (const test of suite.testSuite.tests) {
|
|
40
|
+
if (test.status === 'failed' || test.status === 'timedOut') {
|
|
41
|
+
// only flag as failed if the last attempt has failed
|
|
42
|
+
if (test.retries === test.retry) {
|
|
43
|
+
failures.push({
|
|
44
|
+
test: ResultsParser.getTestName(test),
|
|
45
|
+
failureReason: test.reason,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return failures;
|
|
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
|
+
}
|
|
63
|
+
updateResults(data) {
|
|
64
|
+
if (data.testSuite.tests.length > 0) {
|
|
65
|
+
this.result.push(data);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
addTestResult(suiteName, testCase) {
|
|
69
|
+
const testResults = [];
|
|
70
|
+
for (const result of testCase.results) {
|
|
71
|
+
testResults.push({
|
|
72
|
+
suiteName,
|
|
73
|
+
name: testCase.title,
|
|
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
|
+
: '',
|
|
83
|
+
retry: result.retry,
|
|
84
|
+
retries: testCase.retries,
|
|
85
|
+
startedAt: new Date(result.startTime).toISOString(),
|
|
86
|
+
endedAt: new Date(new Date(result.startTime).getTime() + result.duration).toISOString(),
|
|
87
|
+
reason: this.safelyDetermineFailure(result),
|
|
88
|
+
attachments: result.attachments,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
this.updateResults({
|
|
92
|
+
testSuite: {
|
|
93
|
+
title: suiteName,
|
|
94
|
+
tests: testResults,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
safelyDetermineFailure(result) {
|
|
99
|
+
if (result.errors.length > 0) {
|
|
100
|
+
const fullError = result.errors
|
|
101
|
+
.map((e) => `${e.message}\r\n${e.stack ? e.stack : ''}\r\n`)
|
|
102
|
+
.join();
|
|
103
|
+
return this.cleanseReason(fullError);
|
|
104
|
+
}
|
|
105
|
+
return `${this.cleanseReason(result.error?.message)} \n ${this.cleanseReason(result.error?.stack)}`;
|
|
106
|
+
}
|
|
107
|
+
cleanseReason(rawReaseon) {
|
|
108
|
+
// eslint-disable-next-line prefer-regex-literals
|
|
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');
|
|
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;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exports.default = ResultsParser;
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import { WebClient, KnownBlock, Block, ChatPostMessageResponse } from '@slack/web-api';
|
|
2
|
-
import { SummaryResults } from '.';
|
|
3
|
-
export declare type additionalInfo = Array<{
|
|
4
|
-
key: string;
|
|
5
|
-
value: string;
|
|
6
|
-
}>;
|
|
7
|
-
export default class SlackClient {
|
|
8
|
-
private slackWebClient;
|
|
9
|
-
constructor(slackClient: WebClient);
|
|
10
|
-
sendMessage({ options, }: {
|
|
11
|
-
options: {
|
|
12
|
-
channelIds: Array<string>;
|
|
13
|
-
summaryResults: SummaryResults;
|
|
14
|
-
customLayout: Function | undefined;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
import { WebClient, KnownBlock, Block, ChatPostMessageResponse } from '@slack/web-api';
|
|
2
|
+
import { SummaryResults } from '.';
|
|
3
|
+
export declare type additionalInfo = Array<{
|
|
4
|
+
key: string;
|
|
5
|
+
value: string;
|
|
6
|
+
}>;
|
|
7
|
+
export default class SlackClient {
|
|
8
|
+
private slackWebClient;
|
|
9
|
+
constructor(slackClient: WebClient);
|
|
10
|
+
sendMessage({ options, }: {
|
|
11
|
+
options: {
|
|
12
|
+
channelIds: Array<string>;
|
|
13
|
+
summaryResults: SummaryResults;
|
|
14
|
+
customLayout: Function | undefined;
|
|
15
|
+
customLayoutAsync: Function | undefined;
|
|
16
|
+
fakeRequest?: Function;
|
|
17
|
+
maxNumberOfFailures: number;
|
|
18
|
+
};
|
|
19
|
+
}): Promise<Array<{
|
|
20
|
+
channel: string;
|
|
21
|
+
outcome: string;
|
|
22
|
+
}>>;
|
|
23
|
+
doPostRequest(channel: string, blocks: Array<KnownBlock | Block>): Promise<ChatPostMessageResponse>;
|
|
24
|
+
}
|
package/dist/src/SlackClient.js
CHANGED
|
@@ -1,59 +1,62 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const LayoutGenerator_1 = require("./LayoutGenerator");
|
|
4
|
-
class SlackClient {
|
|
5
|
-
slackWebClient;
|
|
6
|
-
constructor(slackClient) {
|
|
7
|
-
this.slackWebClient = slackClient;
|
|
8
|
-
}
|
|
9
|
-
async sendMessage({ options, }) {
|
|
10
|
-
let blocks;
|
|
11
|
-
if (options.customLayout) {
|
|
12
|
-
blocks = options.customLayout(options.summaryResults);
|
|
13
|
-
}
|
|
14
|
-
else {
|
|
15
|
-
blocks = await
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const LayoutGenerator_1 = require("./LayoutGenerator");
|
|
4
|
+
class SlackClient {
|
|
5
|
+
slackWebClient;
|
|
6
|
+
constructor(slackClient) {
|
|
7
|
+
this.slackWebClient = slackClient;
|
|
8
|
+
}
|
|
9
|
+
async sendMessage({ options, }) {
|
|
10
|
+
let blocks;
|
|
11
|
+
if (options.customLayout) {
|
|
12
|
+
blocks = options.customLayout(options.summaryResults);
|
|
13
|
+
}
|
|
14
|
+
else if (options.customLayoutAsync) {
|
|
15
|
+
blocks = await options.customLayoutAsync(options.summaryResults);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
blocks = await (0, LayoutGenerator_1.default)(options.summaryResults, options.maxNumberOfFailures);
|
|
19
|
+
}
|
|
20
|
+
if (!options.channelIds) {
|
|
21
|
+
throw new Error(`Channel ids [${options.channelIds}] is not valid`);
|
|
22
|
+
}
|
|
23
|
+
const result = [];
|
|
24
|
+
for (const channel of options.channelIds) {
|
|
25
|
+
let chatResponse;
|
|
26
|
+
try {
|
|
27
|
+
// under test
|
|
28
|
+
if (options.fakeRequest) {
|
|
29
|
+
chatResponse = await options.fakeRequest();
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// send request for reals
|
|
33
|
+
chatResponse = await this.doPostRequest(channel, blocks);
|
|
34
|
+
}
|
|
35
|
+
if (chatResponse.ok) {
|
|
36
|
+
result.push({ channel, outcome: `✅ Message sent to ${channel}` });
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.log(`✅ Message sent to ${channel}`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
result.push({ channel, outcome: `❌ Message not sent to ${channel} \r\n ${JSON.stringify(chatResponse, null, 2)}` });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
result.push({
|
|
46
|
+
channel,
|
|
47
|
+
outcome: `❌ Message not sent to ${channel} \r\n ${error.message}`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
async doPostRequest(channel, blocks) {
|
|
54
|
+
const chatResponse = await this.slackWebClient.chat.postMessage({
|
|
55
|
+
channel,
|
|
56
|
+
text: ' ',
|
|
57
|
+
blocks,
|
|
58
|
+
});
|
|
59
|
+
return chatResponse;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.default = SlackClient;
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { FullConfig, Reporter, Suite, TestCase, TestResult } from '@playwright/test/reporter';
|
|
2
|
-
declare class SlackReporter implements Reporter {
|
|
3
|
-
private suite;
|
|
4
|
-
private sendResults;
|
|
5
|
-
private slackChannels;
|
|
6
|
-
private meta;
|
|
7
|
-
private customLayout;
|
|
8
|
-
private
|
|
9
|
-
private
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
import { FullConfig, Reporter, Suite, TestCase, TestResult } from '@playwright/test/reporter';
|
|
2
|
+
declare class SlackReporter implements Reporter {
|
|
3
|
+
private suite;
|
|
4
|
+
private sendResults;
|
|
5
|
+
private slackChannels;
|
|
6
|
+
private meta;
|
|
7
|
+
private customLayout;
|
|
8
|
+
private customLayoutAsync;
|
|
9
|
+
private maxNumberOfFailuresToShow;
|
|
10
|
+
private resultsParser;
|
|
11
|
+
logs: string[];
|
|
12
|
+
onBegin(fullConfig: FullConfig, suite: Suite): void;
|
|
13
|
+
onTestEnd(test: TestCase, result: TestResult): void;
|
|
14
|
+
onEnd(): Promise<void>;
|
|
15
|
+
preChecks(): {
|
|
16
|
+
okToProceed: boolean;
|
|
17
|
+
message?: string;
|
|
18
|
+
};
|
|
19
|
+
log(message: string | undefined): void;
|
|
20
|
+
}
|
|
21
|
+
export default SlackReporter;
|