{"id":13651603,"url":"https://github.com/ryanrosello-og/playwright-slack-report","last_synced_at":"2026-02-21T04:09:43.841Z","repository":{"id":57749461,"uuid":"514423847","full_name":"ryanrosello-og/playwright-slack-report","owner":"ryanrosello-og","description":"Publish your Playwright test results to your favourite Slack channel(s).","archived":false,"fork":false,"pushed_at":"2026-02-14T04:52:42.000Z","size":2699,"stargazers_count":137,"open_issues_count":5,"forks_count":40,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-02-14T12:32:52.449Z","etag":null,"topics":["playwright","slack","test-automation","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ryanrosello-og.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-07-15T22:49:02.000Z","updated_at":"2026-02-14T04:51:32.000Z","dependencies_parsed_at":"2023-12-22T20:52:24.623Z","dependency_job_id":"fbf17241-517f-4409-8fa1-db6a6a982a85","html_url":"https://github.com/ryanrosello-og/playwright-slack-report","commit_stats":{"total_commits":23,"total_committers":4,"mean_commits":5.75,"dds":"0.30434782608695654","last_synced_commit":"dc81339a4c79f9b2da1ffa10b364dcf2930a5695"},"previous_names":[],"tags_count":54,"template":false,"template_full_name":null,"purl":"pkg:github/ryanrosello-og/playwright-slack-report","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanrosello-og%2Fplaywright-slack-report","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanrosello-og%2Fplaywright-slack-report/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanrosello-og%2Fplaywright-slack-report/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanrosello-og%2Fplaywright-slack-report/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ryanrosello-og","download_url":"https://codeload.github.com/ryanrosello-og/playwright-slack-report/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanrosello-og%2Fplaywright-slack-report/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29672913,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T03:11:15.450Z","status":"ssl_error","status_checked_at":"2026-02-21T03:10:34.920Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["playwright","slack","test-automation","typescript"],"created_at":"2024-08-02T02:00:50.834Z","updated_at":"2026-02-21T04:09:43.819Z","avatar_url":"https://github.com/ryanrosello-og.png","language":"TypeScript","funding_links":["https://www.buymeacoffee.com/ryanrosello.og"],"categories":["Reporters"],"sub_categories":[],"readme":"# playwright-slack-report ![Builds](https://github.com/ryanrosello-og/playwright-slack-report/actions/workflows/playwright.yml/badge.svg) [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/ryanrosello-og/playwright-slack-report/blob/master/LICENSE) [![Coverage Status](https://coveralls.io/repos/github/ryanrosello-og/playwright-slack-report/badge.svg?branch=main)](https://coveralls.io/github/ryanrosello-og/playwright-slack-report?branch=main) [![CodeQL](https://github.com/ryanrosello-og/playwright-slack-report/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/ryanrosello-og/playwright-slack-report/actions/workflows/github-code-scanning/codeql) \u003ca href=\"https://www.buymeacoffee.com/ryanrosello.og\"\u003e\u003cimg src=\"https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png\" height=\"20px\"\u003e\u003c/a\u003e\n\n[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/ryanrosello-og/playwright-slack-report)\n\n![Main Logo](https://github.com/ryanrosello-og/playwright-slack-report/blob/main/assets/_logo.png?raw=true)\n\nPublish your Playwright test results to your favorite Slack channel(s).\n\n![Gif](https://github.com/ryanrosello-og/playwright-slack-report/blob/main/assets/2022-08-15_20-22-59.png?raw=true)\n\n## 🚀 Features\n\n- 💌 Send results your Playwright test results to one or more Slack channels\n- 🎚️ Leverage JSON results created by Playwright and seamlessly post them on Slack\n- 📊 Conditionally send results to Slack channels based on test results\n- 📄 Include additional meta information into your test summary e.g. Branch, BuildId etc\n- 🧑‍🎨 Define your own custom Slack message layout!\n\n# 📦 Installation\n\nRun following commands:\n\n**yarn**\n\n`yarn add playwright-slack-report -D`\n\n**npm**\n\n`npm install playwright-slack-report -D`\n\nModify your `playwright.config.ts` file to include the following:\n\n```typescript\n  reporter: [\n    [\n      \"./node_modules/playwright-slack-report/dist/src/SlackReporter.js\",\n      {\n        channels: [\"pw-tests\", \"ci\"], // provide one or more Slack channels\n        sendResults: \"always\", // \"always\" , \"on-failure\", \"off\"\n      },\n    ],\n    [\"dot\"], // other reporters\n  ],\n```\n\n# Option A - send your results via a Slack webhook\n\nEnable incoming webhooks in your Slack workspace by following the steps as per Slack's documentation:\n\nhttps://api.slack.com/messaging/webhooks\n\nOnce you have enabled incoming webhooks, you will need to copy the webhook URL and specify it in the config:\n\n```typescript\n  reporter: [\n    [\n      \"./node_modules/playwright-slack-report/dist/src/SlackReporter.js\",\n      {\n        slackWebHookUrl: \"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX\",\n        sendResults: \"always\", // \"always\" , \"on-failure\", \"off\"\n      },\n    ],\n    [\"dot\"], // other reporters\n  ],\n```\n\n### Note I:\n\nYou will most likely need to have Slack administrator rights to perform the steps above.\n\n### Note II:\n\nSending failure details in a thread is not supported when using webhooks. You will need to use Option B below.\n\n### Note III:\n\nYou can use `slackWebHookChannel: \"pw-tests\"` as an option if you have a single Slack webhook URL that needs to send messages to multiple channels.\n\n# Option B - send your results via a Slack bot user\n\nRun your tests by providing your `SLACK_BOT_USER_OAUTH_TOKEN` as an environment variable or specifying `slackOAuthToken` option in the config:\n\n`SLACK_BOT_USER_OAUTH_TOKEN=[your Slack bot user OAUTH token] npx playwright test`\n\n\u003e **NOTE:** The Slack channel that you specify will need to be _public_, this app will not be able to publish messages to private channels.\n\n---\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e🔎 How do I find my Slack bot oauth token?\u003c/b\u003e\u003c/summary\u003e\n\nYou will need to have Slack administrator rights to perform the steps below.\n\n1. Navigate to https://api.slack.com/apps\n2. Click the Create New App button and select \"From scratch\"\n\n![Navigate to https://api.slack.com/apps](https://github.com/ryanrosello-og/playwright-slack-report/blob/main/assets/2022-08-09_5-37-11.png?raw=true)\n\n3. Input a name for your app and select the target workspace, then click on the **Create App** button\n\n![Input a name for your app and select the target workspace](https://github.com/ryanrosello-og/playwright-slack-report/blob/main/assets/2022-08-09_5-40-51.png?raw=true)\n\n4. Under the Features menu, select **OAuth \u0026 Permissions** and scroll down to **Scopes** section\n\n![Under the Features menu select](https://github.com/ryanrosello-og/playwright-slack-report/blob/main/assets/2022-08-09_5-44-29.png?raw=true)\n\n5. Click the **Add an OAuth Scope** button and select the following scopes:\n\n![Click the Add an OAuth Scope](https://github.com/ryanrosello-og/playwright-slack-report/blob/main/assets/2022-08-09_5-48-30.png?raw=true)\n\n- chat:write\n- chat:write.public\n- chat:write.customize\n\n6. Scroll up to the OAuth Tokens for Your Workspace and click the **Install to Workspace** button\n\n![Install](https://github.com/ryanrosello-og/playwright-slack-report/blob/main/assets/2022-08-09_5-55-22.png?raw=true)\n\n\u003e You will be prompted with the message below, click the Allow button\n\n![click the Allow button](https://github.com/ryanrosello-og/playwright-slack-report/blob/main/assets/2022-08-09_5-49-49.png?raw=true)\n\nThe final step will be to copy the generated Bot User OAuth Token aka `SLACK_BOT_USER_OAUTH_TOKEN`.\n\n\u003e **Treat this token as a secret.**\n\n![Final](https://github.com/ryanrosello-og/playwright-slack-report/blob/main/assets/2022-08-09_5-53-17.png?raw=true)\n\n\u003c/details\u003e\n\n---\n\n# Option C - send your JSON results via CLI\n\nPlaywright now provides a nice way to [merge multiple reports from multiple shards](https://playwright.dev/docs/test-sharding#merging-reports-from-multiple-shards). You can use this feature to generate a single JSON report and then send it to Slack, alleviating the need to have separate messages sent per shard:\n\n`npx playwright merge-reports --reporter json ./all-blob-reports \u003e merged_tests_results.json`\n\n^ It is important that you set the `--reporter` to `json` and pipe the results to a json file, otherwise the report will not be generated in the correct format.\n\nNext, you will need to configure the cli. See example below:\n\n_cli_config.json:_\n\n```json\n{\n  \"sendResults\": \"always\",\n  \"slackLogLevel\": \"error\",\n  \"sendUsingBot\": {\n    \"channels\": [\"demo\"]\n  },\n  \"showInThread\": true,\n  \"meta\": [\n    { \"key\": \"build\", \"value\": \"1.0.0\" },\n    { \"key\": \"branch\", \"value\": \"master\" },\n    { \"key\": \"commit\", \"value\": \"1234567890\" },\n    {\n      \"key\": \"results\",\n      \"value\": \"https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png\"\n    }\n  ],\n  \"maxNumberOfFailures\": 4,\n  \"disableUnfurl\": true\n}\n```\n\nThe config file also supports the follow extra options:\n\n- `proxy` - String representation of your proxy server.\n- `sendUsingWebhook` - Object containing the webhook url to send the results to (see example below)\n- `customLayout` - Object specifying the custom layout relative path and function name:\n\n```\n  \"customLayout\": {\n    \"source\": \"./custom_block/cli_block_with_meta.ts\",\n    \"functionName\": \"generateCustomLayoutSimpleMeta\"\n  }\n```\n\n- `customLayoutAsync` - Similar to `customLayout`, except this key requires an async function:\n\n```\n  \"customLayoutAsync\": {\n    \"source\": \"./custom_block/cli_block_with_meta.ts\",\n    \"functionName\": \"generateCustomAsyncLayoutSimpleMeta\"\n  }\n```\n\nThe customLayout typescript file must export a function name using the `exports` syntax e.g. `exports.generateCustomLayoutSimpleMeta = generateCustomLayoutSimpleMeta;`\nThis file should not contain any `import` statements otherwise it will complain. See example below:\n\n\u003cdetails\u003e\n  \u003csummary\u003eExample custom layout for CLI\u003c/summary\u003e\n\n```js\nfunction generateCustomLayoutSimpleMeta(summaryResults) {\n  const meta = [];\n  if (summaryResults.meta) {\n    for (let i = 0; i \u003c summaryResults.meta.length; i += 1) {\n      const { key, value } = summaryResults.meta[i];\n      meta.push({\n        type: 'section',\n        text: {\n          type: 'mrkdwn',\n          text: `\\n*${key}* :🙌\\t${value}`,\n        },\n      });\n    }\n  }\n  return [\n    {\n      type: 'section',\n      text: {\n        type: 'mrkdwn',\n        text:\n          summaryResults.failed === 0\n            ? ':tada: All tests passed!'\n            : `😭${summaryResults.failed} failure(s) out of ${summaryResults.tests.length} tests`,\n      },\n    },\n    ...meta,\n  ];\n}\n\nasync function generateCustomAsyncLayoutSimpleMeta(summaryResults) {\n  const meta = [];\n  // do some async stuff here\n  if (summaryResults.meta) {\n    for (let i = 0; i \u003c summaryResults.meta.length; i += 1) {\n      const { key, value } = summaryResults.meta[i];\n      meta.push({\n        type: 'section',\n        text: {\n          type: 'mrkdwn',\n          text: `\\n*${key}* :😍\\t${value}`,\n        },\n      });\n    }\n  }\n  return [\n    {\n      type: 'section',\n      text: {\n        type: 'mrkdwn',\n        text:\n          summaryResults.failed === 0\n            ? ':tada: All tests passed!'\n            : `😭${summaryResults.failed} failure(s) out of ${summaryResults.tests.length} tests`,\n      },\n    },\n    ...meta,\n  ];\n}\nexports.generateCustomLayoutSimpleMeta = generateCustomLayoutSimpleMeta;\nexports.generateCustomAsyncLayoutSimpleMeta =\n  generateCustomAsyncLayoutSimpleMeta;\n```\n\n\u003c/details\u003e\n\n_Config with extra options_\n\n```json\n{\n  \"sendResults\": \"always\",\n  \"slackLogLevel\": \"error\",\n  \"proxy\": \"http://proxy.mycompany.com:8080\",\n  \"sendUsingWebhook\": {\n    \"webhookUrl\": \"https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX\"\n  },\n  \"showInThread\": true,\n  \"meta\": [\n    { \"key\": \"build\", \"value\": \"1.0.0\" },\n    { \"key\": \"branch\", \"value\": \"master\" },\n    { \"key\": \"commit\", \"value\": \"1234567890\" },\n    {\n      \"key\": \"results\",\n      \"value\": \"https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png\"\n    }\n  ],\n  \"maxNumberOfFailures\": 4,\n  \"disableUnfurl\": true,\n  \"customLayout\": {\n    \"source\": \"./custom_block/cli_block_with_meta.ts\",\n    \"functionName\": \"generateCustomLayoutSimpleMeta\"\n  }\n}\n```\n\nOnce you have generated the JSON report and defined your config file, you can send it to Slack using the following command:\n\n`SLACK_BOT_USER_OAUTH_TOKEN=[your Slack bot user OAUTH token] npx playwright-slack-report -c cli_config.json -j \u003e merged_tests_results.json`\n\nBoth the `-c` and `-j` options are required. The `-c` option is the path to your config file and the `-j` option is the path to your merged JSON report. You will also need to pipe the output to a json file, using the `\u003e` operator.\n\n### Additional notes\n\n- The config file for the cli app is stand-alone, which means you no longer need to define the Playwright slack reporter in your `playwright.config.ts` file\n- In order to handle dynamic meta data e.g. environment variables storing your build id, branch name etc, you can use the `meta` option in the config file and use the format: `__ENV_VARIABLE_NAME` as its value. This will be replaced with the actual value of the environment variable at runtime. See example below:\n\n```json\n{\n  \"sendResults\": \"always\",\n  \"slackLogLevel\": \"error\",\n  \"sendUsingBot\": {\n    \"channels\": [\"demo\"]\n  },\n  \"showInThread\": true,\n  \"meta\": [\n    { \"key\": \"build\", \"value\": \"__ENV_BUILD_ID\" },\n    { \"key\": \"branch\", \"value\": \"__ENV_BRANCH_NAME\" },\n    { \"key\": \"commit\", \"value\": \"__ENV_COMMIT_ID\" },\n    {\n      \"key\": \"results\",\n      \"value\": \"https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png\"\n    }\n  ],\n  \"maxNumberOfFailures\": 4,\n  \"disableUnfurl\": true\n}\n```\n\nIn your `cli_config.json` file:\n\n`__ENV_BUILD_ID` is equivalent to `process.env.BUILD_ID`. This will be automatically handled for you.\n\nYou will encounter the following error if the environment variable is not defined:\n\n```bash\n❌ Environment variable [blah] was not set.\n        This variable was found in the [meta] section of the config file, ensure the variable is set in your environment.\n```\n\n### Sample Github Actions workflow\n\n```yaml\n  ...\n\n  merge-reports:\n    # Merge reports after playwright-tests, even if some shards have failed\n    if: always()\n    needs: [playwright-tests]\n\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v3\n    - uses: actions/setup-node@v3\n      with:\n        node-version: 18\n    - name: Install dependencies\n      run: npm ci\n\n    - name: Download blob reports from GitHub Actions Artifacts\n      uses: actions/download-artifact@v3\n      with:\n        name: all-blob-reports\n        path: all-blob-reports\n\n    - name: Merge into JSON Report\n      run: npx playwright merge-reports --reporter json ./all-blob-reports \u003e merged_tests_results.json\n\n    - name: View merged results\n      run: cat ${GITHUB_WORKSPACE}/merged_tests_results.json\n\n    - name: Send report to Slack using CLI\n      env:\n        SLACK_BOT_USER_OAUTH_TOKEN: ${{ secrets.SLACK_BOT_USER_OAUTH_TOKEN }}\n      run: npx playwright-slack-report --config=\"${GITHUB_WORKSPACE}/cli_config.json\" --json-results=\"${GITHUB_WORKSPACE}/merged_tests_results.json\"\n  ...\n```\n\n# ⚙️ Configuration (applicable for Option A and Option B)\n\nAn example advanced configuration is shown below:\n\n```typescript\n  import { generateCustomLayout } from \"./my_custom_layout\";\n  import { LogLevel } from '@slack/web-api';\n  ...\n\n  reporter: [\n    [\n      \"./node_modules/playwright-slack-report/dist/src/SlackReporter.js\",\n      {\n        channels: [\"pw-tests\", \"ci\"], // provide one or more Slack channels\n        sendResults: \"always\", // \"always\" , \"on-failure\", \"off\"\n        layout: generateCustomLayout,\n        maxNumberOfFailuresToShow: 4,\n        meta: [\n            {\n                key: 'BUILD_NUMBER',\n                value: '323332-2341',\n            },\n            {\n                key: 'WHATEVER_ENV_VARIABLE',\n                value: process.env.SOME_ENV_VARIABLE, // depending on your CI environment, this can be the branch name, build id, etc\n            },\n            {\n                key: 'HTML Results',\n                value: '\u003chttps://your-build-artifacts.my.company.dev/pw/23887/playwright-report/index.html|📊\u003e',\n            },\n        ],\n        slackOAuthToken: 'YOUR_SLACK_OAUTH_TOKEN',\n        slackLogLevel: LogLevel.DEBUG,\n        disableUnfurl: true,\n        showInThread: true,\n      },\n\n    ],\n  ],\n```\n\n### **channels**\n\nAn array of Slack channels to post to, at least one channel is required\n\n### **onSuccessChannels**\n\n(Optional) An array of Slack channels to post to when tests have passed. Value from `channels` is used if not defined here\n\n### **onFailureChannels**\n\n(Optional) An array of Slack channels to post to when tests have failed. Value from `channels` is used if not defined here\n\n### **sendResults**\n\nCan either be _\"always\"_, _\"on-failure\"_ or _\"off\"_, this configuration is required:\n\n- **always** - will send the results to Slack at completion of the test run\n- **on-failure** - will send the results to Slack only if a test failures are encountered\n- **off** - turns off the reporter, it will not send the results to Slack\n\n### **layout**\n\nA function that returns a layout object, this configuration is optional. See section below for more details.\n\n- meta - an array of meta data to be sent to Slack, this configuration is optional.\n\n### **layoutAsync**\n\nSame as **layout** above, but asynchronous in that it returns a promise.\n\n### **maxNumberOfFailuresToShow**\n\nLimits the number of failures shown in the Slack message, defaults to 10.\n\n### **slackOAuthToken**\n\nInstead of providing an environment variable `SLACK_BOT_USER_OAUTH_TOKEN` you can specify the token in the config in the `slackOAuthToken` field.\n\n### **slackLogLevel** (default LogLevel.DEBUG)\n\nThis option allows you to control slack client severity levels for log entries. It accepts a value from @slack/web-api `LogLevel` enum:\n\n- ERROR\n- WARN\n- INFO\n- DEBUG\n\nExample: `slackLogLevel: \"ERROR\",` will only log errors to the console.\n\n### **disableUnfurl** (default: true)\n\nEnable or disable unfurling of links in Slack messages.\n\n### **showInThread** (default: false)\n\nInstructs the reporter to show the failure details in a thread instead of the main channel.\n\n![Show failures in threads](./assets/threads.png)\n\n### **sendCustomBlocksInThreadAfterIndex** (default: undefined)\n\nInstructs the reporter to send blocks provided by your [custom layout](#-define-your-own-slack-message-custom-layout) to a thread following the index specified. _Example_: \n\n`sendCustomBlocksInThreadAfterIndex: 3`\n\n### **proxy** (optional)\n\nString representation of your proxy server.\n_Example_:\n\n`proxy: \"http://proxy.mycompany.com:8080\",`\n\n### **meta** (default: empty array)\n\nThe meta data to be sent to Slack. This is useful for providing additional context to your test run.\n\n**Examples:**\n\n```typescript\n...\nmeta: [\n  {\n    key: 'Suite',\n    value: 'Nightly full regression',\n  },\n  {\n    key: 'GITHUB_REPOSITORY',\n    value: 'octocat/telsa-ui',\n  },\n  {\n    key: 'GITHUB_REF',\n    value: process.env.GITHUB_REF,\n  },\n],\n...\n```\n\n# 🎨 Define your own Slack message custom layout\n\nYou can define your own Slack message layout to suit your needs.\n\nFirstly, install the necessary type definitions:\n\n`yarn add @slack/types -D`\n\nNext, define your layout function. The signature of this function should adhere to example below:\n\n```typescript\nimport { Block, KnownBlock } from '@slack/types';\nimport { SummaryResults } from 'playwright-slack-report/dist/src';\n\nconst generateCustomLayout = (\n  summaryResults: SummaryResults,\n): Array\u003cKnownBlock | Block\u003e =\u003e {\n  // your implementation goes here\n};\n\nexport default generateCustomLayout;\n```\n\nIn your, `playwright.config.ts` file, add your function into the config.\n\n```typescript\n  import { generateCustomLayout } from \"./my_custom_layout\";\n\n  ...\n\n  reporter: [\n    [\n      \"./node_modules/playwright-slack-report/dist/src/SlackReporter.js\",\n      {\n        channels: [\"pw-tests\", \"ci\"], // provide one or more Slack channels\n        sendResults: \"always\", // \"always\" , \"on-failure\", \"off\"\n        layout: generateCustomLayout,\n        ...\n      },\n    ],\n  ],\n```\n\n\u003e Pro Tip: You can use the [block-kit provided by Slack when creating your layout.](https://app.slack.com/block-kit-builder/)\n\n### Examples:\n\n**Example 1: - very simple summary**\n\n```typescript\nimport { Block, KnownBlock } from '@slack/types';\nimport { SummaryResults } from '..';\n\nexport default function generateCustomLayoutSimpleExample(\n  summaryResults: SummaryResults,\n): Array\u003cBlock | KnownBlock\u003e {\n  return [\n    {\n      type: 'section',\n      text: {\n        type: 'mrkdwn',\n        text:\n          summaryResults.failed === 0\n            ? ':tada: All tests passed!'\n            : `😭${summaryResults.failed} failure(s) out of ${summaryResults.tests.length} tests`,\n      },\n    },\n  ];\n}\n```\n\nGenerates the following message in Slack:\n\n![Final](https://github.com/ryanrosello-og/playwright-slack-report/blob/main/assets/2022-08-13_8-02-54.png?raw=true)\n\n**Example 2: - very simple summary (with Meta information)**\n\nAdd the meta block in your config:\n\n```typescript\n  reporter: [\n    [\n      \"./node_modules/playwright-slack-report/dist/src/SlackReporter.js\",\n      {\n        channels: [\"demo\"],\n        sendResults: \"always\", // \"always\" , \"on-failure\", \"off\",\n        layout: generateCustomLayout,\n        meta: [\n          {\n            key: 'EXAMPLE_META_node_env',\n            value: process.env.HOME ,\n          },\n        ],\n      },\n    ],\n  ],\n```\n\nCreate the function to generate the layout:\n\n```typescript\nimport { Block, KnownBlock } from '@slack/types';\nimport { SummaryResults } from '..';\n\nexport default function generateCustomLayoutSimpleMeta(\n  summaryResults: SummaryResults,\n): Array\u003cBlock | KnownBlock\u003e {\n  const meta: { type: string; text: { type: string; text: string } }[] = [];\n  if (summaryResults.meta) {\n    for (let i = 0; i \u003c summaryResults.meta.length; i += 1) {\n      const { key, value } = summaryResults.meta[i];\n      meta.push({\n        type: 'section',\n        text: {\n          type: 'mrkdwn',\n          text: `\\n*${key}* :\\t${value}`,\n        },\n      });\n    }\n  }\n  return [\n    {\n      type: 'section',\n      text: {\n        type: 'mrkdwn',\n        text:\n          summaryResults.failed === 0\n            ? ':tada: All tests passed!'\n            : `😭${summaryResults.failed} failure(s) out of ${summaryResults.tests.length} tests`,\n      },\n    },\n    ...meta,\n  ];\n}\n```\n\nGenerates the following message in Slack:\n\n![Final](https://github.com/ryanrosello-og/playwright-slack-report/blob/main/assets/2022-08-13_8-17-46.png?raw=true)\n\n**Example 3: - With screenshots and/or recorded videos (using AWS S3)**\n\nIn your, `playwright.config.ts` file, add these params (Make sure you use **layoutAsync** rather than **layout**):\n\n```typescript\n  import { generateCustomLayoutAsync } from \"./my_custom_layout\";\n  ...\n  reporter: [\n    [\n      \"./node_modules/playwright-slack-report/dist/src/SlackReporter.js\",\n      {\n        ...\n        layoutAsync: generateCustomLayoutAsync,\n        ...\n      },\n    ],\n  ],\n  use: {\n    ...\n    screenshot: \"only-on-failure\",\n    video: \"retain-on-failure\",\n    ...\n  },\n```\n\nCreate the function to generate the layout asynchronously in `my_custom_layout.ts`:\n\n```typescript\nimport fs from 'fs';\nimport path from 'path';\nimport { Block, KnownBlock } from '@slack/types';\nimport { SummaryResults } from 'playwright-slack-report/dist/src';\nimport { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';\n\nconst s3Client = new S3Client({\n  credentials: {\n    accessKeyId: process.env.S3_ACCESS_KEY || '',\n    secretAccessKey: process.env.S3_SECRET || '',\n  },\n  region: process.env.S3_REGION,\n});\n\nasync function uploadFile(filePath, fileName) {\n  try {\n    const ext = path.extname(filePath);\n    const name = `${fileName}${ext}`;\n\n    await s3Client.send(\n      new PutObjectCommand({\n        Bucket: process.env.S3_BUCKET,\n        Key: name,\n        Body: fs.createReadStream(filePath),\n      }),\n    );\n\n    return `https://${process.env.S3_BUCKET}.s3.${process.env.S3_REGION}.amazonaws.com/${name}`;\n  } catch (err) {\n    console.log('🔥🔥 Error', err);\n  }\n}\n\nexport async function generateCustomLayoutAsync(\n  summaryResults: SummaryResults,\n): Promise\u003cArray\u003cKnownBlock | Block\u003e\u003e {\n  const { tests } = summaryResults;\n  // create your custom slack blocks\n\n  const header = {\n    type: 'header',\n    text: {\n      type: 'plain_text',\n      text: '🎭 *Playwright E2E Test Results*',\n      emoji: true,\n    },\n  };\n\n  const summary = {\n    type: 'section',\n    text: {\n      type: 'mrkdwn',\n      text: `✅ *${summaryResults.passed}* | ❌ *${summaryResults.failed}* | ⏩ *${summaryResults.skipped}*`,\n    },\n  };\n\n  const fails: Array\u003cKnownBlock | Block\u003e = [];\n\n  for (const t of tests) {\n    if (t.status === 'failed' || t.status === 'timedOut') {\n      fails.push({\n        type: 'section',\n        text: {\n          type: 'mrkdwn',\n          text: `👎 *[${t.browser}] | ${t.suiteName.replace(/\\W/gi, '-')}*`,\n        },\n      });\n\n      const assets: Array\u003cstring\u003e = [];\n\n      if (t.attachments) {\n        for (const a of t.attachments) {\n          // Upload failed tests screenshots and videos to the service of your choice\n          // In my case I upload the to S3 bucket\n          const permalink = await uploadFile(\n            a.path,\n            `${t.suiteName}--${t.name}`.replace(/\\W/gi, '-').toLowerCase(),\n          );\n\n          if (permalink) {\n            let icon = '';\n            if (a.name === 'screenshot') {\n              icon = '📸';\n            } else if (a.name === 'video') {\n              icon = '🎥';\n            }\n\n            assets.push(`${icon}  See the \u003c${permalink}|${a.name}\u003e`);\n          }\n        }\n      }\n\n      if (assets.length \u003e 0) {\n        fails.push({\n          type: 'context',\n          elements: [{ type: 'mrkdwn', text: assets.join('\\n') }],\n        });\n      }\n    }\n  }\n\n  return [header, summary, { type: 'divider' }, ...fails];\n}\n```\n\n**Example 4: - Upload the attachments to directly to Slack**\n\nTo enable this functionality, make sure the slackbot user has the following additional scopes:\n\n- `files:write`\n- `files:read`\n\nYou will need to re-install the app and re-invite the bot into the channel.\n\nThe value of the channel_id should be the channel id of the channel you want to upload the file to. This channel id can be found in the url when you are in the channel. e.g.\n\n**https://app.slack.com/client/T02RVEEFPDH/C05H7TKVDUK**\n\n^ the bit starting with 'C...' is your channel id. In this case, the channel id is `C05H7TKVDUK`\n\n```typescript\nimport web_api_1 from '@slack/web-api';\nimport fs from 'fs';\nconst slackClient = new web_api_1.WebClient(\n  process.env.SLACK_BOT_USER_OAUTH_TOKEN,\n);\n\nasync function uploadFile(\n  filePath: string,\n): Promise\u003cweb_api_1.FilesCompleteUploadExternalResponse[] | undefined\u003e {\n  try {\n    const result = await slackClient.filesUploadV2({\n      channel_id: 'C05H7TKVDUK', // \u003c\u003c this is the channel id not channel name! ☠️\n      file: fs.createReadStream(filePath),\n      filename: filePath.split('/').at(-1),\n    });\n\n    return result.files;\n  } catch (error) {\n    console.log('🔥🔥 error', error);\n  }\n}\n\nexport default async function generateCustomLayout(\n  summaryResults: SummaryResults,\n): Promise\u003c({ type: string; text: { type: string; text: string } } | Block)[]\u003e {\n  const header = {\n    type: 'header',\n    text: {\n      type: 'plain_text',\n      text: '🎭 *Playwright E2E Test Results*',\n      emoji: true,\n    },\n  };\n\n  const summary = {\n    type: 'section',\n    text: {\n      type: 'mrkdwn',\n      text: `✅ *${summaryResults.passed}* | ❌ *${summaryResults.failed}* | ⏩ *${summaryResults.skipped}*`,\n    },\n  };\n\n  const fails: Array\u003cKnownBlock | Block\u003e = [];\n  const { tests } = summaryResults;\n  for (const test of tests) {\n    if (test.attachments) {\n      for (const attachment of test.attachments) {\n        const uploadResult = await uploadFile(attachment.path);\n\n        if (uploadResult \u0026\u0026 uploadResult[0].files) {\n          const { name, permalink } = uploadResult[0].files[0];\n          if (name === 'image' \u0026\u0026 permalink) {\n            fails.push({\n              alt_text: '',\n              image_url: permalink,\n              title: { type: 'plain_text', text: name || '' },\n              type: 'image',\n            });\n          }\n\n          if (name === 'video' \u0026\u0026 permalink) {\n            fails.push({\n              alt_text: '',\n              // NOTE:\n              // Slack requires thumbnail_url length to be more that 0\n              // Either set screenshot url as the thumbnail or add a placeholder image url\n              thumbnail_url: '',\n              title: { type: 'plain_text', text: name || '' },\n              type: 'video',\n              video_url: permalink,\n            });\n          }\n        }\n      }\n    }\n  }\n  return [header, summary, ...fails];\n}\n```\n\n# 🔑 License\n\n[MIT](https://github.com/ryanrosello-og/playwright-slack-report/blob/main/LICENSE)\n\n# ✨ Contributing\n\nClone the project and run `npm install`\n\nMake your changes\nRun the tests using `npm run pw`\n\n**To execute and test the entire package:**\n\nRun `npm pack`\n\nCreate a new playwright project using `yarn create playwright`\nModify the `package.json` and a local dependency to the generated `tgz` file\n\ne.g.\n\n```\n  \"dependencies\": {\n    \"playwright-slack-report\": \"/home/ry/_repo/playwright-slack-report/playwright-slack-report-1.0.3.tgz\"\n  }\n```\n\n- Execute `npm install`\n- Set your `SLACK_BOT_USER_OAUTH_TOKEN` environment variable\n- Modify the `playwright.config.ts` as above\n- Run the tests using `npx playwright text`\n\n# 🐛 Something not working for you?\n\nFeel free to [raise a github issue](https://github.com/ryanrosello-og/playwright-slack-report/issues) for any bugs or feature requests.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanrosello-og%2Fplaywright-slack-report","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fryanrosello-og%2Fplaywright-slack-report","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanrosello-og%2Fplaywright-slack-report/lists"}