diff --git a/package-lock.json b/package-lock.json
index 09bd288..9636089 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "testbeats",
- "version": "2.0.8",
+ "version": "2.0.9",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "testbeats",
- "version": "2.0.8",
+ "version": "2.0.9",
"license": "ISC",
"dependencies": {
"async-retry": "^1.3.3",
@@ -738,9 +738,9 @@
}
},
"node_modules/fast-xml-parser": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz",
- "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz",
+ "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==",
"funding": [
{
"type": "github",
@@ -2440,11 +2440,11 @@
}
},
"node_modules/test-results-parser": {
- "version": "0.1.19",
- "resolved": "https://registry.npmjs.org/test-results-parser/-/test-results-parser-0.1.19.tgz",
- "integrity": "sha512-Q3iAZWRz/DvSS+ecPys29WUsMRybsURwYvkuqdCcmYPHUBcyUPrKDQDGRLSFPMI8b44XfDHh67+RVTFg7mOgQQ==",
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/test-results-parser/-/test-results-parser-0.2.3.tgz",
+ "integrity": "sha512-mTiaR6v2g3vODj1m93mZAUaz03MSNlTn5KKZDEdxJGdi/ydgugLsW8UvsNiyxTmA2AOFoO3iVrUED0PYk7nDFg==",
"dependencies": {
- "fast-xml-parser": "^4.3.2",
+ "fast-xml-parser": "^4.4.1",
"globrex": "^0.1.2",
"html-escaper": "^3.0.3",
"totalist": "^3.0.1"
@@ -3096,9 +3096,9 @@
}
},
"fast-xml-parser": {
- "version": "4.4.0",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz",
- "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==",
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz",
+ "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==",
"requires": {
"strnum": "^1.0.5"
}
@@ -4163,11 +4163,11 @@
}
},
"test-results-parser": {
- "version": "0.1.19",
- "resolved": "https://registry.npmjs.org/test-results-parser/-/test-results-parser-0.1.19.tgz",
- "integrity": "sha512-Q3iAZWRz/DvSS+ecPys29WUsMRybsURwYvkuqdCcmYPHUBcyUPrKDQDGRLSFPMI8b44XfDHh67+RVTFg7mOgQQ==",
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/test-results-parser/-/test-results-parser-0.2.3.tgz",
+ "integrity": "sha512-mTiaR6v2g3vODj1m93mZAUaz03MSNlTn5KKZDEdxJGdi/ydgugLsW8UvsNiyxTmA2AOFoO3iVrUED0PYk7nDFg==",
"requires": {
- "fast-xml-parser": "^4.3.2",
+ "fast-xml-parser": "^4.4.1",
"globrex": "^0.1.2",
"html-escaper": "^3.0.3",
"totalist": "^3.0.1"
diff --git a/package.json b/package.json
index 148bcae..237ea74 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "testbeats",
- "version": "2.0.8",
+ "version": "2.0.9",
"description": "Publish test results to Microsoft Teams, Google Chat, Slack and InfluxDB",
"main": "src/index.js",
"types": "./src/index.d.ts",
diff --git a/src/commands/publish.command.js b/src/commands/publish.command.js
index 03c06af..54feac5 100644
--- a/src/commands/publish.command.js
+++ b/src/commands/publish.command.js
@@ -18,6 +18,7 @@ class PublishCommand {
*/
constructor(opts) {
this.opts = opts;
+ this.errors = [];
}
async publish() {
@@ -31,7 +32,7 @@ class PublishCommand {
this.#validateConfig();
this.#processResults();
await this.#publishResults();
- logger.info('✅ Results published successfully!');
+ await this.#publishErrors();
}
#validateEnvDetails() {
@@ -184,13 +185,24 @@ class PublishCommand {
} else if (result_options.type === 'jmeter') {
this.results.push(prp.parse(result_options));
} else {
- this.results.push(trp.parse(result_options));
+ const { result, errors } = trp.parseV2(result_options);
+ if (result) {
+ this.results.push(result);
+ }
+ if (errors) {
+ this.errors = this.errors.concat(errors);
+ }
}
}
}
}
async #publishResults() {
+ if (!this.results.length) {
+ logger.warn('⚠️ No results to publish');
+ return;
+ }
+
for (const config of this.configs) {
for (let i = 0; i < this.results.length; i++) {
const result = this.results[i];
@@ -207,6 +219,23 @@ class PublishCommand {
}
}
}
+ logger.info('✅ Results published successfully!');
+ }
+
+ async #publishErrors() {
+ if (!this.errors.length) {
+ logger.debug('⚠️ No errors to publish');
+ return;
+ }
+ logger.info('🛑 Publishing errors...');
+ for (const config of this.configs) {
+ if (config.targets) {
+ for (const target of config.targets) {
+ await target_manager.handleErrors({ target, errors: this.errors });
+ }
+ }
+ }
+ throw new Error(this.errors.join('\n'));
}
}
diff --git a/src/targets/chat.js b/src/targets/chat.js
index 0c7c59d..3f85695 100644
--- a/src/targets/chat.js
+++ b/src/targets/chat.js
@@ -238,7 +238,31 @@ const default_inputs = {
]
};
+async function handleErrors({ target, errors }) {
+ let title = 'Error: Reporting Test Results';
+ title = target.inputs.title ? title + ' - ' + target.inputs.title : title;
+
+ const root_payload = getRootPayload();
+ const payload = root_payload.cards[0];
+
+ payload.sections.push({
+ "widgets": [
+ {
+ "textParagraph": {
+ text: `${title}
Errors:
${errors.join('
')}`
+ }
+ }
+ ]
+ });
+
+ return request.post({
+ url: target.inputs.url,
+ body: root_payload
+ });
+}
+
module.exports = {
run,
+ handleErrors,
default_options
}
\ No newline at end of file
diff --git a/src/targets/index.js b/src/targets/index.js
index f344d88..365651b 100644
--- a/src/targets/index.js
+++ b/src/targets/index.js
@@ -34,6 +34,14 @@ async function run(target, result) {
}
}
+async function handleErrors({ target, errors }) {
+ const target_runner = getTargetRunner(target);
+ if (target_runner.handleErrors) {
+ await target_runner.handleErrors({ target, errors });
+ }
+}
+
module.exports = {
- run
+ run,
+ handleErrors
}
\ No newline at end of file
diff --git a/src/targets/slack.js b/src/targets/slack.js
index 78e504b..fc75cfe 100644
--- a/src/targets/slack.js
+++ b/src/targets/slack.js
@@ -264,7 +264,45 @@ const default_inputs = {
]
}
+async function handleErrors({ target, errors }) {
+ let title = 'Error: Reporting Test Results';
+ title = target.inputs.title ? title + ' - ' + target.inputs.title : title;
+
+ const blocks = [];
+
+ blocks.push({
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": title
+ }
+ });
+ blocks.push({
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": errors.join('\n\n')
+ }
+ });
+
+ const payload = {
+ "attachments": [
+ {
+ "color": COLORS.DANGER,
+ "blocks": blocks,
+ "fallback": title,
+ }
+ ]
+ };
+
+ return request.post({
+ url: target.inputs.url,
+ body: payload
+ });
+}
+
module.exports = {
run,
+ handleErrors,
default_options
}
diff --git a/src/targets/teams.js b/src/targets/teams.js
index 293145f..9bd9255 100644
--- a/src/targets/teams.js
+++ b/src/targets/teams.js
@@ -300,7 +300,40 @@ const default_inputs = {
]
}
+async function handleErrors({ target, errors }) {
+ let title = 'Error: Reporting Test Results';
+ title = target.inputs.title ? title + ' - ' + target.inputs.title : title;
+
+ const root_payload = getRootPayload();
+ const payload = getMainPayload(target);
+
+ payload.body.push({
+ "type": "TextBlock",
+ "text": title,
+ "size": "medium",
+ "weight": "bolder",
+ "wrap": true
+ });
+
+ payload.body.push({
+ "type": "TextBlock",
+ "text": errors.join('\n'),
+ "size": "medium",
+ "weight": "bolder",
+ "wrap": true
+ });
+
+ setRootPayload(root_payload, payload);
+
+
+ return request.post({
+ url: target.inputs.url,
+ body: root_payload
+ });
+}
+
module.exports = {
run,
+ handleErrors,
default_options
}
diff --git a/test/handle-errors.spec.js b/test/handle-errors.spec.js
new file mode 100644
index 0000000..915b239
--- /dev/null
+++ b/test/handle-errors.spec.js
@@ -0,0 +1,206 @@
+const { mock } = require('pactum');
+const assert = require('assert');
+const { publish } = require('../src');
+
+describe('handle errors', () => {
+
+ afterEach(() => {
+ mock.clearInteractions();
+ });
+
+ it('should send errors to chat', async () => {
+ const id = mock.addInteraction('post errors to chat');
+ let err;
+ try {
+ await publish({
+ config: {
+ "targets": [
+ {
+ "name": "chat",
+ "inputs": {
+ "url": "http://localhost:9393/message"
+ }
+ }
+ ],
+ "results": [
+ {
+ "type": "testng",
+ "files": [
+ "test/data/testng/invalid.xml"
+ ]
+ }
+ ]
+ }
+ });
+ } catch (e) {
+ err = e;
+ }
+ assert.equal(mock.getInteraction(id).exercised, true);
+ assert.ok(err.toString().includes('invalid.xml'));
+ });
+
+ it('should send errors to teams', async () => {
+ const id = mock.addInteraction('post errors to teams');
+ let err;
+ try {
+ await publish({
+ config: {
+ "targets": [
+ {
+ "name": "teams",
+ "inputs": {
+ "url": "http://localhost:9393/message"
+ }
+ }
+ ],
+ "results": [
+ {
+ "type": "testng",
+ "files": [
+ "test/data/testng/invalid.xml"
+ ]
+ }
+ ]
+ }
+ });
+ } catch (e) {
+ err = e;
+ }
+ assert.equal(mock.getInteraction(id).exercised, true);
+ assert.ok(err.toString().includes('invalid.xml'));
+ });
+
+ it('should send errors to slack', async () => {
+ const id = mock.addInteraction('post errors to slack');
+ let err;
+ try {
+ await publish({
+ config: {
+ "targets": [
+ {
+ "name": "slack",
+ "inputs": {
+ "url": "http://localhost:9393/message"
+ }
+ }
+ ],
+ "results": [
+ {
+ "type": "testng",
+ "files": [
+ "test/data/testng/invalid.xml"
+ ]
+ }
+ ]
+ }
+ });
+ } catch (e) {
+ err = e;
+ }
+ assert.equal(mock.getInteraction(id).exercised, true);
+ assert.ok(err.toString().includes('invalid.xml'));
+ });
+
+ it('should send results and errors to chat', async () => {
+ const id1 = mock.addInteraction('post test-summary to chat');
+ const id2 = mock.addInteraction('post errors to chat');
+ let err;
+ try {
+ await publish({
+ config: {
+ "targets": [
+ {
+ "name": "chat",
+ "inputs": {
+ "url": "http://localhost:9393/message"
+ }
+ }
+ ],
+ "results": [
+ {
+ "type": "testng",
+ "files": [
+ "test/data/testng/single-suite.xml",
+ "test/data/testng/invalid.xml"
+ ]
+ }
+ ]
+ }
+ });
+ } catch (e) {
+ err = e;
+ }
+ assert.equal(mock.getInteraction(id1).exercised, true);
+ assert.equal(mock.getInteraction(id2).exercised, true);
+ assert.ok(err.toString().includes('invalid.xml'));
+ });
+
+ it('should send results and errors to teams', async () => {
+ const id1 = mock.addInteraction('post test-summary to teams');
+ const id2 = mock.addInteraction('post errors to teams');
+ let err;
+ try {
+ await publish({
+ config: {
+ "targets": [
+ {
+ "name": "teams",
+ "inputs": {
+ "url": "http://localhost:9393/message"
+ }
+ }
+ ],
+ "results": [
+ {
+ "type": "testng",
+ "files": [
+ "test/data/testng/single-suite.xml",
+ "test/data/testng/invalid.xml"
+ ]
+ }
+ ]
+ }
+ });
+ } catch (e) {
+ err = e;
+ }
+ assert.equal(mock.getInteraction(id1).exercised, true);
+ assert.equal(mock.getInteraction(id2).exercised, true);
+ assert.ok(err.toString().includes('invalid.xml'));
+ });
+
+ it('should send results and errors to slack', async () => {
+ const id1 = mock.addInteraction('post test-summary to slack');
+ const id2 = mock.addInteraction('post errors to slack');
+ let err;
+ try {
+ await publish({
+ config: {
+ "targets": [
+ {
+ "name": "slack",
+ "inputs": {
+ "url": "http://localhost:9393/message"
+ }
+ }
+ ],
+ "results": [
+ {
+ "type": "testng",
+ "files": [
+ "test/data/testng/single-suite.xml",
+ "test/data/testng/invalid.xml"
+ ]
+ }
+ ]
+ }
+ });
+ } catch (e) {
+ err = e;
+ }
+ assert.equal(mock.getInteraction(id1).exercised, true);
+ assert.equal(mock.getInteraction(id2).exercised, true);
+ assert.ok(err.toString().includes('invalid.xml'));
+ });
+
+});
\ No newline at end of file
diff --git a/test/mocks/chat.mock.js b/test/mocks/chat.mock.js
index 1d92aaa..6424aae 100644
--- a/test/mocks/chat.mock.js
+++ b/test/mocks/chat.mock.js
@@ -1,5 +1,6 @@
const { addInteractionHandler } = require('pactum').handler;
const { addDataTemplate } = require('pactum').stash;
+const { includes } = require('pactum-matchers');
addDataTemplate({
'CHAT_RESULT_SINGLE_SUITE': {
@@ -580,4 +581,34 @@ addInteractionHandler('post test-summary with ci-info to chat', () => {
status: 200
}
}
+});
+
+addInteractionHandler('post errors to chat', () => {
+ return {
+ strict: false,
+ request: {
+ method: 'POST',
+ path: '/message',
+ body: {
+ "cards": [
+ {
+ "sections": [
+ {
+ "widgets": [
+ {
+ "textParagraph": {
+ "text": includes('invalid.xml')
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ },
+ response: {
+ status: 200
+ }
+ }
});
\ No newline at end of file
diff --git a/test/mocks/slack.mock.js b/test/mocks/slack.mock.js
index ab64f89..a8a3119 100644
--- a/test/mocks/slack.mock.js
+++ b/test/mocks/slack.mock.js
@@ -1,5 +1,6 @@
const { addInteractionHandler } = require('pactum').handler;
const { addDataTemplate } = require('pactum').stash;
+const { includes } = require('pactum-matchers');
addDataTemplate({
'SLACK_ROOT_SINGLE_SUITE': {
@@ -396,7 +397,6 @@ addInteractionHandler('post test-summary with mentions to slack', () => {
}
});
-
addInteractionHandler('post test-summary with mentions group name to slack', () => {
return {
request: {
@@ -793,4 +793,41 @@ addInteractionHandler('post test-summary to slack with max suites as 1', () => {
status: 200
}
}
+});
+
+addInteractionHandler('post errors to slack', () => {
+ return {
+ strict: false,
+ request: {
+ method: 'POST',
+ path: '/message',
+ body: {
+ "attachments": [
+ {
+ "color": "#DC143C",
+ "blocks": [
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": "Error: Reporting Test Results"
+ }
+ },
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": includes('invalid.xml')
+ }
+ }
+ ],
+ "fallback": "Error: Reporting Test Results"
+ }
+ ]
+ }
+ },
+ response: {
+ status: 200
+ }
+ }
});
\ No newline at end of file
diff --git a/test/mocks/teams.mock.js b/test/mocks/teams.mock.js
index b1d055d..2f986a4 100644
--- a/test/mocks/teams.mock.js
+++ b/test/mocks/teams.mock.js
@@ -1,5 +1,6 @@
const { addInteractionHandler } = require('pactum').handler;
const { addDataTemplate } = require('pactum').stash;
+const { includes } = require('pactum-matchers');
addDataTemplate({
'TEAMS_ROOT_TITLE_SINGLE_SUITE': {
@@ -1724,4 +1725,47 @@ addInteractionHandler('post test-summary with metadata and hyperlinks to teams',
status: 200
}
}
+});
+
+addInteractionHandler('post errors to teams', () => {
+ return {
+ strict: false,
+ 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": [
+ {
+ "type": "TextBlock",
+ "text": "Error: Reporting Test Results",
+ "size": "medium",
+ "weight": "bolder",
+ "wrap": true
+ },
+ {
+ "type": "TextBlock",
+ "text": includes('invalid.xml'),
+ "size": "medium",
+ "weight": "bolder",
+ "wrap": true
+ }
+ ],
+ "actions": []
+ }
+ }
+ ]
+ }
+ },
+ response: {
+ status: 200
+ }
+ }
});
\ No newline at end of file