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

Use Postman API to find collection & environment in Slack command #7

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
.vscode
.vscode
.env
24 changes: 6 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,11 @@ I've managed to workaround this and use my local machine as the host, by using t

Clone this `basic-newman-slack-bot` repo and install all the npm modules using the `npm install` command in a terminal.

The `basic-newman-slack-bot` has been pre-loaded with a few example files, these files can be found in the `./collections` and `./environments` folders:

```sh
- collections
- Restful_Booker_Collection.json
- environments
- Local_Restful_Booker_Environment.json
- Staging_Restful_Booker_Environment.json
- Production_Restful_Booker_Environment.json
```
The `basic-newman-slack-bot` will search in your Postman workspace for the collection and the environment to execute tests. All you need is a valid API key. You can get your key from the [integrations dashboard](https://go.postman.co/integrations/services/pm_pro_api).

We'll be using the [Postman echo](https://docs.postman-echo.com/?version=latest), this is a publicly available Collection released by Postman team. It includes many examples in test scripts.

These files will _tell_ `newman` where to make the requests too. We'll be using the [Restful-Booker API](https://restful-booker.herokuapp.com/), this is a publicly available set of endpoints, that I had no control over so it might be worth doing a quick check first, just to know that the API is alive....or you might see a lot of test failures.
Create an environment file with the name : `.env` and add a variable `API_KEY` with the value of the api key generated previously in Postman website.

In the same terminal, navigate to the cloned directory and start the `express` server using the `npm start` command. This will start the app on port `3000`.

Expand All @@ -49,8 +42,6 @@ Running the app locally using Docker can achieved using the `docker-compose up`

![Running Locally With Docker](./public/Running_Locally_With_Docker.png)

**Note** - If you make changes to any of the files or add things like your own `collection` or `environment` files, you will need to run the `docker-compose build` command, for the changes to take effect.

---

## Installing the Newman Runner app in Slack
Expand Down Expand Up @@ -115,7 +106,7 @@ If there are any test failures from the Newman Run, these will be listed in the

![Test Run Failures](./public/Slack_Bot_Failures.PNG)

If the Newman Run failed before running the Collection or there was a syntax error within a test etc. This will return a `Newman Error` message with a description of the error:
If the Newman Run failed before running the Collection (couldn't find the collection or the environment in your workspace) or there was a syntax error within a test etc. This will return a `Newman Error` message with a description of the error:

![Newman Run Error](./public/Slack_Bot_Newman_Fail.PNG)

Expand Down Expand Up @@ -206,16 +197,13 @@ The Slack message output looks the _same_ but the `title` is now a hyperlink tha

![Dashboard Report](./public/Dashboard_Template.PNG)

The report is created using an optional custom template file, this can be found in the `./reports/templates` folder. The `newman-reporter-htmlextra` reporter will just create a default styled report if you don't add the `template` property.

To use a different template, you will need to change the path of the `template` option:

```javascript
reporters: ['htmlextra'],
reporter: {
htmlextra: {
export: './reports/htmlResults.html',
template: '<path to template>'
export: './reports/htmlResults.html'
}
}
```
Expand Down
216 changes: 132 additions & 84 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ const path = require('path')
const app = express()

app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json({ limit: "50mb" }));
app.use(express.static('reports'));

const apikey = process.env.API_KEY

class TestRunContext {
constructor(newmanResult) {
this.collection = newmanResult.collection.name;
this.environment = newmanResult.environment.name
this.iterationCount = newmanResult.run.stats.iterations.total
this.start = new Date(newmanResult.run.timings.started)
Expand All @@ -27,11 +31,15 @@ class TestRunContext {
get percentagePassed() {
return (this.testResultTotal * 100 / (this.testResultTotal + this.testResultFailed)).toFixed(2)
}

get envFileName() {
return this.environment === undefined ? "No Environment file specified for the Newman Run" : this.environment
get environmentName() {
return this.environment === undefined ? "No Environment specified for the Newman Run" : this.environment;
}

get collectionName() {
return this.collection === undefined ? "No Collection for the Newman Run" : this.collection;
}

get skippedList() {
if(this.skipped === undefined) {
return "No Skipped Tests"
Expand Down Expand Up @@ -81,8 +89,8 @@ class TestRunContext {
"fallback": "Newman Run Summary",
"color": `${this.colour}`,
"title": "Summary Test Result",
"title_link": "https://newman-app.localtunnel.me/htmlResults.html",
"text": `Environment File: *${this.envFileName}*\nTotal Run Duration: *${this.runDuration}*`,
"title_link": "http://newman-app.serverless.social/htmlResults.html",
"text": `Collection : *${this.collectionName}* \nEnvironment : *${this.environmentName}*\nTotal Run Duration: *${this.runDuration}*`,
"mrkdwn": true,
"fields": [
{
Expand Down Expand Up @@ -131,13 +139,48 @@ class TestRunContext {
}
}

let executeNewman = (environmentFile, iterationCount) => {
let getCollectionUid = collectionName => {
const errorMessage = 'The Collection name provided is not found in your workspace. Please try again';
return new Promise((resolve, reject) => {
axios({
method: "GET",
url: `https://api.getpostman.com/collections/?apikey=${apikey}`
})
.then(response => {
collection = response.data.collections.find(collection => collection.name === collectionName);
collection != undefined ? resolve(collection.uid) : reject(errorMessage);
})
.catch(error => {
reject(error);
});
});
};

let getEnvironmentUid = environmentName => {
const errorMessage = 'The Environment name provided is not found in your workspace. Please try again';
return new Promise((resolve, reject) => {
axios({
method: "get",
url:
`https://api.getpostman.com/environments/?apikey=${apikey}`
})
.then(response => {
environment = response.data.environments.find( environment => environment.name === environmentName);
environment != undefined ? resolve(environment.uid) : reject(errorMessage);
})
.catch(error => {
reject(error);
});
});
};

let executeNewman = (collectionUid, environmentUid, iterationCount) => {
return new Promise((resolve, reject) => {
newman.run({
collection: './collections/Restful_Booker_Collection.json',
environment: environmentFile,
collection: `https://api.getpostman.com/collections/${collectionUid}?apikey=${apikey}`,
environment: `https://api.getpostman.com/environments/${environmentUid}?apikey=${apikey}`,
iterationCount: iterationCount,
reporters: ['htmlextra'],
reporters: ['cli', 'htmlextra'],
reporter: {
htmlextra: {
export: './reports/htmlResults.html'
Expand All @@ -152,21 +195,21 @@ let executeNewman = (environmentFile, iterationCount) => {
})
}

function InvalidName(responseURL, message, res) {
function InvalidArgument(responseURL, argName, message, res) {
axios({
method: 'post',
url: `${responseURL}`,
headers: { "Content-Type": "application/json" },
headers: { 'Content-Type': 'application/json' },
data: {
"response_type": "in_channel",
"attachments": [
'response_type': 'in_channel',
'attachments': [
{
"color": "danger",
"title": "Invalid Environment",
"mrkdwn": true,
"fields": [
'color': 'danger',
'title': `Invalid ${argName}`,
'mrkdwn': true,
'fields': [
{
"value": `${message}`
'value': `${message}`
}
]
}
Expand All @@ -182,81 +225,86 @@ app.post("/newmanRun", (req, res) => {
const responseURL = req.body.response_url
const channelText = req.body.text

const enteredEnv = (channelText).split(" ")[0]
const iterationCount = parseInt((channelText).split(" ")[1])

const filename = `./environments/${enteredEnv}_Restful_Booker_Environment.json`

const fileNameCheck = fs.existsSync(filename)

if (channelText.length === 0) {

message = "Please enter an valid *Environment* name."

return InvalidName(responseURL, message, res)

} else if (fileNameCheck === false) {
const collectionName = channelText.split(" & ")[0];
const environmentName = channelText.split(" & ")[1];
const iterationCount = parseInt(channelText.split(" & ")[2]);

message = `Could not find the *${path.basename(filename)}* environment file. Please try again.`

return InvalidName(responseURL, message, res)

} else {
environmentFile = filename
if (channelText.length === 0) {
message = "Please enter a valid *Command* .";
return InvalidArgument(responseURL,'Slack command', message, res);
} else if (collectionName === undefined || environmentName === undefined) {
message = `Could not find the Collection or the Environment in your command. Please try again.`;
return InvalidArgument(responseURL, 'Slack command', message, res);
}

axios({
method: 'post',
url: `${responseURL}`,
headers: { "Content-Type": "application/json" },
data: {
"response_type": "in_channel",
"attachments": [
{
"color": "good",
"title": "Newman Test Run Started",
"mrkdwn": true,
"fields": [
getCollectionUid(collectionName)
.then(collectionUid => {
getEnvironmentUid(environmentName)
.then(environmentUid => {
axios({
'method': "post",
'url': `${responseURL}`,
'headers': { "Content-Type": "application/json" },
'data': {
'response_type': "in_channel",
'attachments': [
{
"value": `Your Summary Report for the *${enteredEnv}* environment will be with you _very_ soon`
'color': "good",
'title': "Newman Test Run Started",
'mrkdwn': true,
'fields': [
{
'value': `Your Summary Report for the collection *${collectionName}* in *${environmentName}* environment will be with you _very_ soon`
}
]
}
]
}
]
}
})
.then(res.status(202).end())
.then(() => executeNewman(environmentFile, iterationCount))
.then(newmanResult => { return new TestRunContext(newmanResult) })
.then(context => {
return axios({
method: 'post',
url: `${responseURL}`,
headers: { "Content-Type": "application/json" },
data: context.slackData
})
})
.catch(err => {
axios({
method: 'post',
url: `${responseURL}`,
headers: { "Content-Type": "application/json" },
data: {
"response_type": "in_channel",
"attachments": [
{
"color": "danger",
"title": "Newman Run Error",
"fields": [
{
"value": `${err}`
})
.then(res.status(202).end())
.then(() => {
executeNewman(collectionUid, environmentUid, iterationCount)
.then(newmanResult => {
return new TestRunContext(newmanResult);
})
.then(context => {
return axios({
'method': "post",
'url': `${responseURL}`,
'headers': { "Content-Type": "application/json" },
'data': context.slackData
});
})
.catch(err => {
axios({
method: "post",
url: `${responseURL}`,
headers: { "Content-Type": "application/json" },
data: {
response_type: "in_channel",
attachments: [
{
color: "danger",
title: "Newman Run Error",
fields: [
{
value: `${err}`
}
]
}
]
}
]
}
]
}
})
});
});
});
})
.catch(error => {
InvalidArgument(responseURL, "Environment", error, res);
});
})
.catch(error => {
InvalidArgument(responseURL, "Collection", error, res);
});
})
const port = Number(process.env.PORT || 3000)
app.listen(port)
Expand Down
Loading