playwright-slack-report 1.1.21 → 1.1.23

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 CHANGED
@@ -42,7 +42,35 @@ Modify your `playwright.config.ts` file to include the following:
42
42
  ["dot"], // other reporters
43
43
  ],
44
44
  ```
45
+ # Option A - send your results via a Slack webhook
45
46
 
47
+ Enable incoming webhooks in your Slack workspace by following the steps as per Slack's documentation:
48
+
49
+ https://api.slack.com/messaging/webhooks
50
+
51
+
52
+
53
+ Once you have enabled incoming webhooks, you will need to copy the webhook URL and specify it in the config:
54
+
55
+ ```typescript
56
+ reporter: [
57
+ [
58
+ "./node_modules/playwright-slack-report/dist/src/SlackReporter.js",
59
+ {
60
+ slackWebHookUrl: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX",
61
+ sendResults: "always", // "always" , "on-failure", "off"
62
+ },
63
+ ],
64
+ ["dot"], // other reporters
65
+ ],
66
+ ```
67
+ ### Note I:
68
+ You will most likely need to have Slack administrator rights to perform the steps above.
69
+
70
+ ### Note II:
71
+ Sending failure details in a thread is not supported when using webhooks. You will need to use Option B below.
72
+
73
+ # Option B
46
74
  Run your tests by providing your `SLACK_BOT_USER_OAUTH_TOKEN` as an environment variable or specifying `slackOAuthToken` option in the config:
47
75
 
48
76
  `SLACK_BOT_USER_OAUTH_TOKEN=[your Slack bot user OAUTH token] npx playwright test`
@@ -2,8 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateFailures = exports.generateBlocks = void 0;
4
4
  const generateBlocks = async (summaryResults, maxNumberOfFailures) => {
5
- const maxNumberOfFailureLength = 650;
6
- const fails = [];
7
5
  const meta = [];
8
6
  const header = {
9
7
  type: 'section',
@@ -19,32 +17,7 @@ const generateBlocks = async (summaryResults, maxNumberOfFailures) => {
19
17
  text: `✅ *${summaryResults.passed}* | ❌ *${summaryResults.failed}* | ⏩ *${summaryResults.skipped}*`,
20
18
  },
21
19
  };
22
- for (let i = 0; i < summaryResults.failures.length; i += 1) {
23
- const { failureReason, test } = summaryResults.failures[i];
24
- const formattedFailure = failureReason
25
- .substring(0, maxNumberOfFailureLength)
26
- .split('\n')
27
- .map((l) => `>${l}`)
28
- .join('\n');
29
- fails.push({
30
- type: 'section',
31
- text: {
32
- type: 'mrkdwn',
33
- text: `*${test}*
34
- \n${formattedFailure}`,
35
- },
36
- });
37
- if (i > maxNumberOfFailures) {
38
- fails.push({
39
- type: 'section',
40
- text: {
41
- type: 'mrkdwn',
42
- text: `*There are too many failures to display - ${fails.length} out of ${summaryResults.failures.length} failures shown*`,
43
- },
44
- });
45
- break;
46
- }
47
- }
20
+ const fails = await generateFailures(summaryResults, maxNumberOfFailures);
48
21
  if (summaryResults.meta) {
49
22
  for (let i = 0; i < summaryResults.meta.length; i += 1) {
50
23
  const { key, value } = summaryResults.meta[i];
@@ -61,9 +34,6 @@ const generateBlocks = async (summaryResults, maxNumberOfFailures) => {
61
34
  header,
62
35
  summary,
63
36
  ...meta,
64
- {
65
- type: 'divider',
66
- },
67
37
  ...fails,
68
38
  ];
69
39
  };
@@ -71,7 +41,8 @@ exports.generateBlocks = generateBlocks;
71
41
  const generateFailures = async (summaryResults, maxNumberOfFailures) => {
72
42
  const maxNumberOfFailureLength = 650;
73
43
  const fails = [];
74
- for (let i = 0; i < summaryResults.failures.length; i += 1) {
44
+ const numberOfFailuresToShow = Math.min(summaryResults.failures.length, maxNumberOfFailures);
45
+ for (let i = 0; i < numberOfFailuresToShow; i += 1) {
75
46
  const { failureReason, test } = summaryResults.failures[i];
76
47
  const formattedFailure = failureReason
77
48
  .substring(0, maxNumberOfFailureLength)
@@ -86,16 +57,15 @@ const generateFailures = async (summaryResults, maxNumberOfFailures) => {
86
57
  \n${formattedFailure}`,
87
58
  },
88
59
  });
89
- if (i > maxNumberOfFailures) {
90
- fails.push({
91
- type: 'section',
92
- text: {
93
- type: 'mrkdwn',
94
- text: `*There are too many failures to display - ${fails.length} out of ${summaryResults.failures.length} failures shown*`,
95
- },
96
- });
97
- break;
98
- }
60
+ }
61
+ if (summaryResults.failures.length > maxNumberOfFailures) {
62
+ fails.push({
63
+ type: 'section',
64
+ text: {
65
+ type: 'mrkdwn',
66
+ text: `*⚠️ There are too many failures to display - ${fails.length} out of ${summaryResults.failures.length} failures shown*`,
67
+ },
68
+ });
99
69
  }
100
70
  return [
101
71
  {
@@ -10,6 +10,7 @@ declare class SlackReporter implements Reporter {
10
10
  private slackChannels;
11
11
  private slackLogLevel;
12
12
  private slackOAuthToken;
13
+ private slackWebHookUrl;
13
14
  private disableUnfurl;
14
15
  private proxy;
15
16
  private browsers;
@@ -2,8 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const web_api_1 = require("@slack/web-api");
4
4
  const https_proxy_agent_1 = require("https-proxy-agent");
5
+ const webhook_1 = require("@slack/webhook");
5
6
  const ResultsParser_1 = require("./ResultsParser");
6
7
  const SlackClient_1 = require("./SlackClient");
8
+ const SlackWebhookClient_1 = require("./SlackWebhookClient");
7
9
  class SlackReporter {
8
10
  customLayout;
9
11
  customLayoutAsync;
@@ -15,6 +17,7 @@ class SlackReporter {
15
17
  slackChannels = [];
16
18
  slackLogLevel;
17
19
  slackOAuthToken;
20
+ slackWebHookUrl;
18
21
  disableUnfurl;
19
22
  proxy;
20
23
  browsers = [];
@@ -29,7 +32,12 @@ class SlackReporter {
29
32
  }
30
33
  else {
31
34
  // eslint-disable-next-line max-len
32
- this.browsers = fullConfig.projects.map((obj) => ({ projectName: obj.name, browser: obj.use.browserName ? obj.use.browserName : obj.use.defaultBrowserType }));
35
+ this.browsers = fullConfig.projects.map((obj) => ({
36
+ projectName: obj.name,
37
+ browser: obj.use.browserName
38
+ ? obj.use.browserName
39
+ : obj.use.defaultBrowserType,
40
+ }));
33
41
  }
34
42
  if (slackReporterConfig) {
35
43
  this.meta = slackReporterConfig.meta || [];
@@ -37,8 +45,10 @@ class SlackReporter {
37
45
  this.customLayout = slackReporterConfig.layout;
38
46
  this.customLayoutAsync = slackReporterConfig.layoutAsync;
39
47
  this.slackChannels = slackReporterConfig.channels;
40
- this.maxNumberOfFailuresToShow = slackReporterConfig.maxNumberOfFailuresToShow || 10;
48
+ this.maxNumberOfFailuresToShow
49
+ = slackReporterConfig.maxNumberOfFailuresToShow || 10;
41
50
  this.slackOAuthToken = slackReporterConfig.slackOAuthToken || undefined;
51
+ this.slackWebHookUrl = slackReporterConfig.slackWebHookUrl || undefined;
42
52
  this.disableUnfurl = slackReporterConfig.disableUnfurl || false;
43
53
  this.showInThread = slackReporterConfig.showInThread || false;
44
54
  this.slackLogLevel = slackReporterConfig.slackLogLevel || web_api_1.LogLevel.DEBUG;
@@ -66,40 +76,67 @@ class SlackReporter {
66
76
  return;
67
77
  }
68
78
  const agent = this.proxy ? new https_proxy_agent_1.HttpsProxyAgent(this.proxy) : undefined;
69
- const slackClient = new SlackClient_1.default(new web_api_1.WebClient(this.slackOAuthToken || process.env.SLACK_BOT_USER_OAUTH_TOKEN, {
70
- logLevel: this.slackLogLevel || web_api_1.LogLevel.DEBUG,
71
- agent,
72
- }));
73
- const result = await slackClient.sendMessage({
74
- options: {
75
- channelIds: this.slackChannels,
79
+ if (this.slackWebHookUrl) {
80
+ const webhook = new webhook_1.IncomingWebhook(this.slackWebHookUrl, { agent });
81
+ const slackWebhookClient = new SlackWebhookClient_1.default(webhook);
82
+ const webhookResult = await slackWebhookClient.sendMessage({
76
83
  customLayout: this.customLayout,
77
84
  customLayoutAsync: this.customLayoutAsync,
78
85
  maxNumberOfFailures: this.maxNumberOfFailuresToShow,
79
86
  disableUnfurl: this.disableUnfurl,
80
87
  summaryResults: resultSummary,
81
- showInThread: this.showInThread,
82
- },
83
- });
84
- // eslint-disable-next-line no-console
85
- console.log(JSON.stringify(result, null, 2));
86
- if (this.showInThread && resultSummary.failures.length > 0) {
87
- await slackClient.attachDetailsToThread({
88
- channelIds: this.slackChannels,
89
- ts: result[0].ts,
90
- summaryResults: resultSummary,
91
- maxNumberOfFailures: this.maxNumberOfFailuresToShow,
92
88
  });
89
+ // eslint-disable-next-line no-console
90
+ console.log(JSON.stringify(webhookResult, null, 2));
91
+ }
92
+ else {
93
+ const slackClient = new SlackClient_1.default(new web_api_1.WebClient(this.slackOAuthToken || process.env.SLACK_BOT_USER_OAUTH_TOKEN, {
94
+ logLevel: this.slackLogLevel || web_api_1.LogLevel.DEBUG,
95
+ agent,
96
+ }));
97
+ const result = await slackClient.sendMessage({
98
+ options: {
99
+ channelIds: this.slackChannels,
100
+ customLayout: this.customLayout,
101
+ customLayoutAsync: this.customLayoutAsync,
102
+ maxNumberOfFailures: this.maxNumberOfFailuresToShow,
103
+ disableUnfurl: this.disableUnfurl,
104
+ summaryResults: resultSummary,
105
+ showInThread: this.showInThread,
106
+ },
107
+ });
108
+ // eslint-disable-next-line no-console
109
+ console.log(JSON.stringify(result, null, 2));
110
+ if (this.showInThread && resultSummary.failures.length > 0) {
111
+ for (let i = 0; i < result.length; i += 1) {
112
+ // eslint-disable-next-line no-await-in-loop
113
+ await slackClient.attachDetailsToThread({
114
+ channelIds: [result[i].channel],
115
+ ts: result[i].ts,
116
+ summaryResults: resultSummary,
117
+ maxNumberOfFailures: this.maxNumberOfFailuresToShow,
118
+ });
119
+ }
120
+ }
93
121
  }
94
122
  }
95
123
  preChecks() {
96
124
  if (this.sendResults === 'off') {
97
125
  return { okToProceed: false, message: '❌ Slack reporter is disabled' };
98
126
  }
99
- if (!this.slackOAuthToken && !process.env.SLACK_BOT_USER_OAUTH_TOKEN) {
127
+ if (!this.slackWebHookUrl
128
+ && !this.slackOAuthToken
129
+ && !process.env.SLACK_BOT_USER_OAUTH_TOKEN) {
130
+ return {
131
+ okToProceed: false,
132
+ message: '❌ Neither slack webhook url, slackOAuthToken nor process.env.SLACK_BOT_USER_OAUTH_TOKEN were found',
133
+ };
134
+ }
135
+ if (this.slackWebHookUrl
136
+ && (process.env.SLACK_BOT_USER_OAUTH_TOKEN || this.slackOAuthToken)) {
100
137
  return {
101
138
  okToProceed: false,
102
- message: '❌ Neither slackOAuthToken nor process.env.SLACK_BOT_USER_OAUTH_TOKEN were found',
139
+ message: '❌ You can only enable a single option, either provide a slack webhook url, slackOAuthToken or process.env.SLACK_BOT_USER_OAUTH_TOKEN were found',
103
140
  };
104
141
  }
105
142
  if (!this.sendResults
@@ -0,0 +1,15 @@
1
+ import { IncomingWebhook } from '@slack/webhook';
2
+ import { SummaryResults } from '.';
3
+ export default class SlackWebhookClient {
4
+ private webhook;
5
+ constructor(webhook: IncomingWebhook);
6
+ sendMessage({ customLayout, customLayoutAsync, maxNumberOfFailures, summaryResults, disableUnfurl, }: {
7
+ customLayout: Function | undefined;
8
+ customLayoutAsync: Function | undefined;
9
+ maxNumberOfFailures: number;
10
+ summaryResults: SummaryResults;
11
+ disableUnfurl: boolean;
12
+ }): Promise<{
13
+ outcome: string;
14
+ }>;
15
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const LayoutGenerator_1 = require("./LayoutGenerator");
4
+ class SlackWebhookClient {
5
+ webhook;
6
+ constructor(webhook) {
7
+ this.webhook = webhook;
8
+ }
9
+ async sendMessage({ customLayout, customLayoutAsync, maxNumberOfFailures, summaryResults, disableUnfurl, }) {
10
+ let blocks;
11
+ if (customLayout) {
12
+ blocks = customLayout(summaryResults);
13
+ }
14
+ else if (customLayoutAsync) {
15
+ blocks = await customLayoutAsync(summaryResults);
16
+ }
17
+ else {
18
+ blocks = await (0, LayoutGenerator_1.generateBlocks)(summaryResults, maxNumberOfFailures);
19
+ }
20
+ let result;
21
+ try {
22
+ result = await this.webhook.send({
23
+ blocks,
24
+ unfurl_links: !disableUnfurl,
25
+ });
26
+ }
27
+ catch (error) {
28
+ return {
29
+ outcome: `error: ${JSON.stringify(error, null, 2)}`,
30
+ };
31
+ }
32
+ if (result && result.text === 'ok') {
33
+ return {
34
+ outcome: result.text,
35
+ };
36
+ }
37
+ return {
38
+ outcome: '😵 Failed to send webhook message, ensure your webhook url is valid',
39
+ };
40
+ }
41
+ }
42
+ exports.default = SlackWebhookClient;
@@ -25,7 +25,7 @@ const generateCustomLayout = (summaryResults) => {
25
25
  type: 'section',
26
26
  text: {
27
27
  type: 'mrkdwn',
28
- text: '*There are too many failures to display, view the full results in BuildKite*',
28
+ text: '*⚠️ There are too many failures to display, view the full results in BuildKite*',
29
29
  },
30
30
  });
31
31
  break;
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "dependencies": {
3
3
  "@slack/web-api": "^6.8.1",
4
+ "@slack/webhook": "^6.1.0",
4
5
  "https-proxy-agent": "^7.0.1"
5
6
  },
6
7
  "devDependencies": {
@@ -18,7 +19,7 @@
18
19
  "eslint-plugin-prettier": "^4.2.1",
19
20
  "nyc": "^15.1.0",
20
21
  "prettier": "^2.7.1",
21
- "ts-mockito": "^2.6.1",
22
+ "ts-sinon": "^2.0.2",
22
23
  "typescript": "^4.7.4"
23
24
  },
24
25
  "scripts": {
@@ -28,7 +29,7 @@
28
29
  "lint": "npx eslint . --ext .ts"
29
30
  },
30
31
  "name": "playwright-slack-report",
31
- "version": "1.1.21",
32
+ "version": "1.1.23",
32
33
  "main": "index.js",
33
34
  "types": "dist/index.d.ts",
34
35
  "repository": "git@github.com:ryanrosello-og/playwright-slack-report.git",