Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor beats #183

Merged
merged 3 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 10 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
> !IMPORTANT - This npm package has been renamed from [test-results-reporter](https://www.npmjs.com/package/test-results-reporter) to [testbeats](https://www.npmjs.com/package/testbeats). test-results-reporter will soon be phased out, and users are encouraged to transition to testbeats.
> This npm package has been renamed from [test-results-reporter](https://www.npmjs.com/package/test-results-reporter) to [testbeats](https://www.npmjs.com/package/testbeats). test-results-reporter will soon be phased out, and users are encouraged to transition to testbeats.

<span align="center">

Expand All @@ -19,35 +19,23 @@

<hr>

### Targets

<img height="48" style="margin: 6px;" src="./assets/slack.png" alt="slack" /> <img height="48" style="margin: 6px;" src="./assets/teams.png" alt="teams" /> <img height="48" style="margin: 6px;" src="./assets/chat.png" alt="chat" />

### Extensions

<img height="48" style="margin: 6px;" src="./assets/reportportal.jpeg" alt="reportportal" /> <img height="48" style="margin: 6px;" src="./assets/quickchart.png" alt="quickchart" /> <img height="48" style="margin: 6px;" src="./assets/hyperlink.png" alt="hyperlink" /> <img height="48" style="margin: 6px;" src="./assets/mentions.png" alt="mentions" />

### Test Results

<img height="48" style="margin: 6px;" src="./assets/testng.png" alt="testng" /> <img height="48" style="margin: 6px;" src="./assets/junit.png" alt="junit" /> <img height="48" style="margin: 6px;" src="./assets/cucumber.png" alt="cucumber" /> <img height="48" style="margin: 6px;" src="./assets/mocha.png" alt="mocha" /> <img height="48" style="margin: 6px;" src="./assets/xunit.png" alt="xunit" /> <img height="48" style="margin: 6px;" src="./assets/jmeter.png" alt="jmeter" />
</span>

<hr>
### Get Started

## Sample Reports
TestBeats is a tool designed to streamline the process of publishing test results from various automation testing frameworks to communication platforms like **slack**, **teams** and more for easy access and collaboration. It unifies your test reporting to build quality insights and make faster decisions.

<br>
Read more about the project at [https://testbeats.com](https://testbeats.com)

![teams-summary-report](https://github.com/test-results-reporter/testbeats/raw/main/assets/teams-qc.png)
### Sample Reports

![slack-summary-report](https://github.com/test-results-reporter/testbeats/raw/main/assets/slack-report-portal-analysis.png)
#### Alerts in Slack

<br />
![testbeats-failure-summary](./assets/testbeats-slack-failure-summary.png)

<hr >
#### Results in Portal

# [Documentation](https://test-results-reporter.github.io/)

</span>
![testbeats-failure-summary](./assets/testbeats-failure-summary.png)

<br />

Expand Down
Binary file added assets/testbeats-failure-summary.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/testbeats-slack-failure-summary.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "testbeats",
"version": "2.0.1",
"version": "2.0.2",
"description": "Publish test results to Microsoft Teams, Google Chat, Slack and InfluxDB",
"main": "src/index.js",
"types": "./src/index.d.ts",
Expand Down
40 changes: 40 additions & 0 deletions src/beats/beats.api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const request = require('phin-retry');

class BeatsApi {

/**
* @param {import('../index').PublishReport} config
*/
constructor(config) {
this.config = config;
}

postTestRun(payload) {
return request.post({
url: `${this.getBaseUrl()}/api/core/v1/test-runs`,
headers: {
'x-api-key': this.config.api_key
},
body: payload
});
}

/**
* @param {string} run_id
* @returns
*/
getTestRun(run_id) {
return request.get({
url: `${this.getBaseUrl()}/api/core/v1/test-runs/key?id=${run_id}`,
headers: {
'x-api-key': this.config.api_key
}
});
}

getBaseUrl() {
return process.env.TEST_BEATS_URL || "https://app.testbeats.com";
}
}

module.exports = { BeatsApi }
151 changes: 151 additions & 0 deletions src/beats/beats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
const { getCIInformation } = require('../helpers/ci');
const logger = require('../utils/logger');
const { BeatsApi } = require('./beats.api');
const { HOOK } = require('../helpers/constants');

class Beats {

/**
* @param {import('../index').PublishReport} config
* @param {TestResult} result
*/
constructor(config, result) {
this.config = config;
this.result = result;
this.api = new BeatsApi(config);
this.test_run_id = '';
}

async publish() {
this.#setCIInfo();
this.#setProjectName();
this.#setRunName();
this.#setApiKey();
await this.#publishTestResults();
this.#updateTitleLink();
await this.#attachFailureSummary();
}

#setCIInfo() {
this.ci = getCIInformation();
}

#setProjectName() {
this.config.project = this.config.project || process.env.TEST_BEATS_PROJECT || (this.ci && this.ci.repository_name) || 'demo-project';
}

#setApiKey() {
this.config.api_key = this.config.api_key || process.env.TEST_BEATS_API_KEY;
}

#setRunName() {
this.config.run = this.config.run || process.env.TEST_BEATS_RUN || (this.ci && this.ci.build_name) || 'demo-run';
}

async #publishTestResults() {
if (!this.config.api_key) {
logger.warn('😿 No API key provided, skipping publishing results to TestBeats Portal');
return;
}
logger.info("🚀 Publishing results to TestBeats Portal...");
try {
const payload = this.#getPayload();
const response = await this.api.postTestRun(payload);
this.test_run_id = response.id;
} catch (error) {
logger.error(`❌ Unable to publish results to TestBeats Portal: ${error.message}`, error);
}
}

#getPayload() {
const payload = {
project: this.config.project,
run: this.config.run,
...this.result
}
if (this.ci) {
payload.ci_details = [this.ci];
}
return payload;
}

#updateTitleLink() {
if (!this.test_run_id) {
return;
}
if (!this.config.targets) {
return;
}
const link = `${this.api.getBaseUrl()}/reports/${this.test_run_id}`;
for (const target of this.config.targets) {
target.inputs.title_link = link;
}
}

async #attachFailureSummary() {
if (!this.test_run_id) {
return;
}
if (!this.config.targets) {
return;
}
if (this.result.status !== 'FAIL') {
return;
}
if (this.config.show_failure_summary === false) {
return;
}
const text = await this.#getFailureSummary();
if (!text) {
return;
}
const extension = this.#getAIFailureSummaryExtension(text);
for (const target of this.config.targets) {
target.extensions = target.extensions || [];
target.extensions.push(extension);
}
}

async #getFailureSummary() {
logger.info('✨ Fetching AI Failure Summary...');
let retry = 3;
while (retry >= 0) {
retry = retry - 1;
await new Promise(resolve => setTimeout(resolve, this.#getDelay()));
const test_run = await this.api.getTestRun(this.test_run_id);
const status = test_run && test_run.failure_summary_status;
switch (status) {
case 'COMPLETED':
return test_run.execution_metrics[0].failure_summary;
case 'FAILED':
logger.error(`❌ Failed to generate AI Failure Summary`);
return;
case 'SKIPPED':
logger.warn(`❗ Skipped generating AI Failure Summary`);
return;
}
logger.info(`🔄 AI Failure Summary not generated, retrying...`);
}
logger.warn(`🙈 AI Failure Summary not generated in given time`);
}

#getDelay() {
if (process.env.TEST_BEATS_DELAY) {
return parseInt(process.env.TEST_BEATS_DELAY);
}
return 3000;
}

#getAIFailureSummaryExtension(text) {
return {
name: 'ai-failure-summary',
hook: HOOK.AFTER_SUMMARY,
inputs: {
failure_summary: text
}
};
}

}

module.exports = { Beats }
Loading
Loading