Repository: test-results-reporter/reporter
Pull request #102 (2022-10-29 08:40:25)

30 extension metadata


Patch:
diff --git a/package-lock.json b/package-lock.json
index d3893fb..f42d5d6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "test-results-reporter",
-  "version": "1.0.14",
+  "version": "1.0.15",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
diff --git a/package.json b/package.json
index 17a3bda..f44a72f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "test-results-reporter",
-  "version": "1.0.14",
+  "version": "1.0.15",
   "description": "Publish test results to Microsoft Teams, Google Chat and Slack",
   "main": "src/index.js",
   "types": "./src/index.d.ts",
diff --git a/src/extensions/index.js b/src/extensions/index.js
index d6e3d04..6f7c341 100644
--- a/src/extensions/index.js
+++ b/src/extensions/index.js
@@ -5,6 +5,7 @@ const rp_history = require('./report-portal-history');
 const qc_test_summary = require('./quick-chart-test-summary');
 const percy_analysis = require('./percy-analysis');
 const custom = require('./custom');
+const metadata = require('./metadata');
 const { EXTENSION } = require('../helpers/constants');
 const { checkCondition } = require('../helpers/helper');
 
@@ -47,6 +48,8 @@ function getExtensionRunner(extension) {
       return percy_analysis;
     case EXTENSION.CUSTOM:
       return custom;
+    case EXTENSION.METADATA:
+      return metadata;
     default:
       return require(extension.name);
   }
diff --git a/src/extensions/metadata.js b/src/extensions/metadata.js
new file mode 100644
index 0000000..7469b51
--- /dev/null
+++ b/src/extensions/metadata.js
@@ -0,0 +1,119 @@
+const { HOOK, STATUS } = require('../helpers/constants');
+const { addExtension } = require('../helpers/teams');
+const { addTextBlock } = require('../helpers/slack');
+const { addTextSection } = require('../helpers/chat');
+const { checkCondition } = require('../helpers/helper');
+
+/**
+ * @param {object} param0
+ * @param {import('..').Target} param0.target
+ * @param {import('..').MetadataExtension} param0.extension
+ */
+async function run({ target, extension, result, payload, root_payload }) {
+  if (target.name === 'teams') {
+    extension.inputs = Object.assign({}, default_inputs_teams, extension.inputs);
+    await attachForTeams({ extension, payload, result });
+  } else if (target.name === 'slack') {
+    extension.inputs = Object.assign({}, default_inputs_slack, extension.inputs);
+    await attachForSlack({ extension, payload, result });
+  } else if (target.name === 'chat') {
+    extension.inputs = Object.assign({}, default_inputs_chat, extension.inputs);
+    await attachForChat({ extension, payload, result });
+  }
+}
+
+/**
+ * @param {object} param0
+ * @param {import('..').MetadataExtension} param0.extension
+ */
+async function attachForTeams({ extension, payload, result }) {
+  const valid_data = await getValidData({ extension, result });
+  if (valid_data.length > 0) {
+    const data = [];
+    for (const current of valid_data) {
+      if (current.type === 'hyperlink') {
+        data.push(`[${current.key}](${current.value})`);
+      } else if (current.key) {
+        data.push(`**${current.key}:** ${current.value}`);
+      } else {
+        data.push(current.value);
+      }
+    }
+    addExtension({ payload, extension, text: data.join(' ｜ ') });
+  }
+}
+
+async function attachForSlack({ extension, payload, result }) {
+  const valid_data = await getValidData({ extension, result });
+  if (valid_data.length > 0) {
+    const data = [];
+    for (const current of valid_data) {
+      if (current.type === 'hyperlink') {
+        data.push(`<${current.value}|${current.key}>`);
+      } else if (current.key) {
+        data.push(`*${current.key}:* ${current.value}`);
+      } else {
+        data.push(current.value);
+      }
+    }
+    addTextBlock({ payload, extension, text: data.join(' ｜ ') });
+  }
+}
+
+async function attachForChat({ extension, payload, result }) {
+  const valid_data = await getValidData({ extension, result });
+  if (valid_data.length > 0) {
+    const data = [];
+    for (const current of valid_data) {
+      if (current.type === 'hyperlink') {
+        data.push(`<a href="${current.value}">${current.key}</a>`);
+      } else if (current.key) {
+        data.push(`<b>${current.key}:</b> ${current.value}`);
+      } else {
+        data.push(current.value);
+      }
+    }
+    addTextSection({ payload, extension, text: data.join(' ｜ ') });
+  }
+}
+
+/**
+ * @param {object} param0
+ * @param {import('..').MetadataExtension} param0.extension
+ */
+async function getValidData({ extension, result }) {
+  const valid_data = [];
+  for (const current of extension.inputs.data) {
+    const condition = current.condition || default_options.condition;
+    if (await checkCondition({ condition, result })) {
+      valid_data.push(current);
+    }
+  }
+  return valid_data;
+}
+
+
+const default_options = {
+  hook: HOOK.END,
+  condition: STATUS.PASS_OR_FAIL
+}
+
+const default_inputs_teams = {
+  title: '',
+  separator: true
+}
+
+const default_inputs_slack = {
+  title: '',
+  separator: false
+}
+
+const default_inputs_chat = {
+  title: '',
+  separator: true
+}
+
+module.exports = {
+  run,
+  default_options
+}
\ No newline at end of file
diff --git a/src/helpers/constants.js b/src/helpers/constants.js
index a0e33ea..b07837a 100644
--- a/src/helpers/constants.js
+++ b/src/helpers/constants.js
@@ -25,7 +25,8 @@ const EXTENSION = Object.freeze({
   REPORT_PORTAL_HISTORY: 'report-portal-history',
   QUICK_CHART_TEST_SUMMARY: 'quick-chart-test-summary',
   PERCY_ANALYSIS: 'percy-analysis',
-  CUSTOM: 'custom'
+  CUSTOM: 'custom',
+  METADATA: 'metadata'
 });
 
 const URLS = Object.freeze({
diff --git a/src/helpers/helper.js b/src/helpers/helper.js
index 89f2e8b..3aeb388 100644
--- a/src/helpers/helper.js
+++ b/src/helpers/helper.js
@@ -78,7 +78,7 @@ function getResultText({ result }) {
  */
 async function checkCondition({ condition, result, target, extension }) {
   if (typeof condition === 'function') {
-    return await condition({target, result, extension });
+    return await condition({ target, result, extension });
   } else {
     const lower_condition = condition.toLowerCase();
     if (ALLOWED_CONDITIONS.has(lower_condition)) {
diff --git a/src/index.d.ts b/src/index.d.ts
index abcbd56..bbf11d6 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -4,9 +4,9 @@ import { PerformanceParseOptions } from 'performance-results-parser';
 import { ParseOptions } from 'test-results-parser';
 import PerformanceTestResult from 'performance-results-parser/src/models/PerformanceTestResult';
 
-export type ExtensionName = 'report-portal-analysis' | 'hyperlinks' | 'mentions' | 'report-portal-history' | 'quick-chart-test-summary' | 'custom';
+export type ExtensionName = 'report-portal-analysis' | 'hyperlinks' | 'mentions' | 'report-portal-history' | 'quick-chart-test-summary' | 'metadata' | 'custom';
 export type Hook = 'start' | 'end';
-export type TargetName = 'slack' | 'teams' | 'chat' | 'custom';
+export type TargetName = 'slack' | 'teams' | 'chat' | 'custom' | 'delay';
 export type PublishReportType = 'test-summary' | 'test-summary-slim' | 'failure-details';
 
 export interface ConditionFunctionContext {
@@ -31,17 +31,18 @@ export interface ReportPortalAnalysisInputs extends ExtensionInputs {
   url: string;
   api_key: string;
   project: string;
-  launch_id: string;
-  launch_name: string;
+  launch_id?: string;
+  launch_name?: string;
 }
 
 export interface ReportPortalHistoryInputs extends ExtensionInputs {
   url: string;
   api_key: string;
   project: string;
-  launch_id: string;
-  launch_name: string;
-  history_depth: number;
+  launch_id?: string;
+  launch_name?: string;
+  history_depth?: number;
+  link_history_via?: string;
 }
 
 export interface QuickChartTestSummaryInputs {
@@ -57,7 +58,7 @@ export interface Extension {
   name: ExtensionName;
   condition?: Condition;
   hook?: Hook;
-  inputs?: ReportPortalAnalysisInputs | ReportPortalHistoryInputs | HyperlinkInputs | MentionInputs | QuickChartTestSummaryInputs | PercyAnalysisInputs | CustomExtensionInputs;
+  inputs?: ReportPortalAnalysisInputs | ReportPortalHistoryInputs | HyperlinkInputs | MentionInputs | QuickChartTestSummaryInputs | PercyAnalysisInputs | CustomExtensionInputs | MetadataInputs;
 }
 
 export interface PercyAnalysisInputs extends ExtensionInputs {
@@ -68,7 +69,7 @@ export interface PercyAnalysisInputs extends ExtensionInputs {
   project_id?: string;
   project_name?: string;
   organization_uid?: string;
-  title_link_to_build: boolean;
+  title_link_to_build?: boolean;
 }
 
 export interface PercyAnalysisOutputs {
@@ -122,6 +123,21 @@ export interface HyperlinksExtension extends Extension {
   inputs?: HyperlinkInputs;
 }
 
+export interface Metadata {
+  key?: string;
+  value: string;
+  type?: string;
+  condition?: Condition;
+}
+
+export interface MetadataInputs extends ExtensionInputs {
+  data?: Metadata[];
+}
+
+export interface MetadataExtension extends Extension {
+  inputs?: MetadataInputs;
+}
+
 /**
  * Targets
  */
diff --git a/src/targets/chat.js b/src/targets/chat.js
index 455636f..754c157 100644
--- a/src/targets/chat.js
+++ b/src/targets/chat.js
@@ -1,7 +1,7 @@
 const request = require('phin-retry');
-const { getTitleText, getResultText, getPercentage, truncate, getPrettyDuration } = require('../helpers/helper');
+const { getTitleText, getResultText, truncate, getPrettyDuration } = require('../helpers/helper');
 const extension_manager = require('../extensions');
-const { HOOK } = require('../helpers/constants');
+const { HOOK, STATUS } = require('../helpers/constants');
 const PerformanceTestResult = require('performance-results-parser/src/models/PerformanceTestResult');
 const { getValidMetrics, getMetricValuesText } = require('../helpers/performance');
 
@@ -213,7 +213,7 @@ async function getTransactionSummary({ target, transaction }) {
 
 
 const default_options = {
-  condition: 'passOrFail'
+  condition: STATUS.PASS_OR_FAIL
 };
 
 const default_inputs = {
diff --git a/src/targets/custom.js b/src/targets/custom.js
index b791755..6b023c9 100644
--- a/src/targets/custom.js
+++ b/src/targets/custom.js
@@ -1,4 +1,5 @@
 const path = require('path');
+const { STATUS } = require('../helpers/constants');
 
 /**
  * 
@@ -18,7 +19,7 @@ async function run({result, target}) {
 }
 
 const default_options = {
-  condition: 'passOrFail'
+  condition: STATUS.PASS_OR_FAIL
 }
 
 module.exports = {
diff --git a/src/targets/delay.js b/src/targets/delay.js
index b8e0ef6..8023d64 100644
--- a/src/targets/delay.js
+++ b/src/targets/delay.js
@@ -1,10 +1,12 @@
+const { STATUS } = require("../helpers/constants");
+
 async function run({ target }) {
   target.inputs = Object.assign({}, default_inputs, target.inputs);
   await new Promise(resolve => setTimeout(resolve, target.inputs.seconds * 1000));
 }
 
 const default_options = {
-  condition: 'passOrFail'
+  condition: STATUS.PASS_OR_FAIL
 }
 
 const default_inputs = {
diff --git a/src/targets/slack.js b/src/targets/slack.js
index 8600720..5ed36bc 100644
--- a/src/targets/slack.js
+++ b/src/targets/slack.js
@@ -1,7 +1,7 @@
 const request = require('phin-retry');
 const { getPercentage, truncate, getPrettyDuration } = require('../helpers/helper');
 const extension_manager = require('../extensions');
-const { HOOK } = require('../helpers/constants');
+const { HOOK, STATUS } = require('../helpers/constants');
 
 const PerformanceTestResult = require('performance-results-parser/src/models/PerformanceTestResult');
 const { getValidMetrics, getMetricValuesText } = require('../helpers/performance');
@@ -236,7 +236,7 @@ async function setTransactionBlock({ result, target, payload }) {
 
 
 const default_options = {
-  condition: 'passOrFail'
+  condition: STATUS.PASS_OR_FAIL
 }
 
 const default_inputs = {
diff --git a/src/targets/teams.js b/src/targets/teams.js
index 0cb8430..b28c7f5 100644
--- a/src/targets/teams.js
+++ b/src/targets/teams.js
@@ -2,7 +2,7 @@ const request = require('phin-retry');
 const { getPercentage, truncate, getPrettyDuration } = require('../helpers/helper');
 const { getValidMetrics, getMetricValuesText } = require('../helpers/performance');
 const extension_manager = require('../extensions');
-const { HOOK } = require('../helpers/constants');
+const { HOOK, STATUS } = require('../helpers/constants');
 
 const TestResult = require('test-results-parser/src/models/TestResult');
 const PerformanceTestResult = require('performance-results-parser/src/models/PerformanceTestResult');
@@ -273,7 +273,7 @@ async function setTransactionBlock({ result, target, payload }) {
 }
 
 const default_options = {
-  condition: 'passOrFail'
+  condition: STATUS.PASS_OR_FAIL
 }
 
 const default_inputs = {


Test patch:
diff --git a/test/ext.metadata.spec.js b/test/ext.metadata.spec.js
new file mode 100644
index 0000000..200ac2a
--- /dev/null
+++ b/test/ext.metadata.spec.js
@@ -0,0 +1,173 @@
+const { mock } = require('pactum');
+const assert = require('assert');
+const { publish, defineConfig } = require('../src');
+
+describe('extensions - metadata', () => {
+
+  it('should send test-summary with metadata to teams', async () => {
+    const id = mock.addInteraction('post test-summary with metadata to teams');
+    await publish({
+      config: {
+        "reports": [
+          {
+            "targets": [
+              {
+                "name": "teams",
+                "inputs": {
+                  "url": "http://localhost:9393/message"
+                },
+                "extensions": [
+                  {
+                    "name": "metadata",
+                    "inputs": {
+                      "data": [
+                        {
+                          "key": "Browser",
+                          "value": "Chrome"
+                        },
+                        {
+                          "value": "1920*1080"
+                        },
+                        {
+                          "value": "1920*1080",
+                          "condition": "never"
+                        },
+                        {
+                          "key": "Pipeline",
+                          "value": "some-url",
+                          "type": "hyperlink"
+                        },
+                      ]
+                    }
+                  }
+                ]
+              }
+            ],
+            "results": [
+              {
+                "type": "testng",
+                "files": [
+                  "test/data/testng/single-suite.xml"
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    });
+    assert.equal(mock.getInteraction(id).exercised, true);
+  });
+
+  it('should send test-summary with metadata to slack', async () => {
+    const id = mock.addInteraction('post test-summary with metadata to slack');
+    await publish({
+      config: {
+        "reports": [
+          {
+            "targets": [
+              {
+                "name": "slack",
+                "inputs": {
+                  "url": "http://localhost:9393/message"
+                },
+                "extensions": [
+                  {
+                    "name": "metadata",
+                    "inputs": {
+                      "data": [
+                        {
+                          "key": "Browser",
+                          "value": "Chrome"
+                        },
+                        {
+                          "value": "1920*1080"
+                        },
+                        {
+                          "value": "1920*1080",
+                          "condition": "never"
+                        },
+                        {
+                          "key": "Pipeline",
+                          "value": "some-url",
+                          "type": "hyperlink"
+                        },
+                      ]
+                    }
+                  }
+                ]
+              }
+            ],
+            "results": [
+              {
+                "type": "testng",
+                "files": [
+                  "test/data/testng/single-suite.xml"
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    });
+    assert.equal(mock.getInteraction(id).exercised, true);
+  });
+
+  it('should send test-summary with metadata to chat', async () => {
+    const id = mock.addInteraction('post test-summary with metadata to chat');
+    await publish({
+      config: {
+        "reports": [
+          {
+            "targets": [
+              {
+                "name": "chat",
+                "inputs": {
+                  "url": "http://localhost:9393/message"
+                },
+                "extensions": [
+                  {
+                    "name": "metadata",
+                    "inputs": {
+                      "data": [
+                        {
+                          "key": "Browser",
+                          "value": "Chrome"
+                        },
+                        {
+                          "value": "1920*1080"
+                        },
+                        {
+                          "value": "1920*1080",
+                          "condition": "never"
+                        },
+                        {
+                          "key": "Pipeline",
+                          "value": "some-url",
+                          "type": "hyperlink"
+                        },
+                      ]
+                    }
+                  }
+                ]
+              }
+            ],
+            "results": [
+              {
+                "type": "testng",
+                "files": [
+                  "test/data/testng/single-suite.xml"
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    });
+    assert.equal(mock.getInteraction(id).exercised, true);
+  });
+
+  afterEach(() => {
+    mock.clearInteractions();
+  });
+
+});
\ No newline at end of file
diff --git a/test/mocks/chat.mock.js b/test/mocks/chat.mock.js
index 7ebfb67..f9bbfb2 100644
--- a/test/mocks/chat.mock.js
+++ b/test/mocks/chat.mock.js
@@ -516,4 +516,36 @@ addInteractionHandler('post test-summary with failures to chat for failed JMeter
       status: 200
     }
   }
+});
+
+addInteractionHandler('post test-summary with metadata to chat', () => {
+  return {
+    request: {
+      method: 'POST',
+      path: '/message',
+      body: {
+        "cards": [
+          {
+            "sections": [
+              {
+                "@DATA:TEMPLATE@": "CHAT_RESULT_SINGLE_SUITE"
+              },
+              {
+                "widgets": [
+                  {
+                    "textParagraph": {
+                      "text": "<b>Browser:</b> Chrome ｜ 1920*1080 ｜ <a href=\"some-url\">Pipeline</a>"
+                    }
+                  }
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    },
+    response: {
+      status: 200
+    }
+  }
 });
\ No newline at end of file
diff --git a/test/mocks/slack.mock.js b/test/mocks/slack.mock.js
index e8bed1b..45101f8 100644
--- a/test/mocks/slack.mock.js
+++ b/test/mocks/slack.mock.js
@@ -581,4 +581,35 @@ addInteractionHandler('post test-summary with failures to slack for failed JMete
       status: 200
     }
   }
+});
+
+addInteractionHandler('post test-summary with metadata to slack', () => {
+  return {
+    request: {
+      method: 'POST',
+      path: '/message',
+      body: {
+        "attachments": [
+          {
+            "color": "#36A64F",
+            "blocks": [
+              {
+                "@DATA:TEMPLATE@": "SLACK_ROOT_SINGLE_SUITE"
+              },
+              {
+                "type": "section",
+                "text": {
+                  "type": "mrkdwn",
+                  "text": "*Browser:* Chrome ｜ 1920*1080 ｜ <some-url|Pipeline>"
+                }
+              }
+            ]
+          }
+        ]
+      }
+    },
+    response: {
+      status: 200
+    }
+  }
 });
\ No newline at end of file
diff --git a/test/mocks/teams.mock.js b/test/mocks/teams.mock.js
index 4e0798b..075af69 100644
--- a/test/mocks/teams.mock.js
+++ b/test/mocks/teams.mock.js
@@ -1513,4 +1513,44 @@ addInteractionHandler('post test-summary to teams with filtered metrics and fiel
       status: 200
     }
   }
+});
+
+addInteractionHandler('post test-summary with metadata to teams', () => {
+  return {
+    request: {
+      method: 'POST',
+      path: '/message',
+      body: {
+        "type": "message",
+        "attachments": [
+          {
+            "contentType": "application/vnd.microsoft.card.adaptive",
+            "content": {
+              "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
+              "type": "AdaptiveCard",
+              "version": "1.0",
+              "body": [
+                {
+                  "@DATA:TEMPLATE@": "TEAMS_ROOT_TITLE_SINGLE_SUITE"
+                },
+                {
+                  "@DATA:TEMPLATE@": "TEAMS_ROOT_RESULTS_SINGLE_SUITE",
+                },
+                {
+                  "type": "TextBlock",
+                  "text": "**Browser:** Chrome ｜ 1920*1080 ｜ [Pipeline](some-url)",
+                  "wrap": true,
+                  "separator": true
+                }
+              ],
+              "actions": []
+            }
+          }
+        ]
+      }
+    },
+    response: {
+      status: 200
+    }
+  }
 });
\ No newline at end of file