Automating your API Testing with Postman and Testrail On AWS Fargate

Let’s automate some API testing. The testing strategy uses AWS ECS Fargate, AWS CodeBuild and CodePipeline for continuous integration and delivery, Postman for testing, and TestRail for reporting. At the end of this article, I’ll give a Terraform example to make it easy to reproduce this test-automation solution on your AWS infrastructure. If you like to read code more than you like to read blog posts, you can find the full implementation here.

Header Image with Logos from Postman, Testrail, and Fargate

AWS ECS is a fully managed container orchestration service. We like to use AWS ECS Fargate, which is a serverless compute service for containers that removes the need to provision and manage servers.

To manage builds and continuously deliver testing automation, we use AWS CodePipeline and AWS CodeBuild.

  • CodeBuild is a fully-managed continuous integration (CI) service that compiles source code.
  • CodePipeline is a continuous delivery (CD) pipeline that automates builds with CodeBuild.

Postman is an API development tool that makes it easy to test and develop your APIs. Postman supports a command-line tool called Newman which allows you to run and test a Postman Collection directly from the command-line. It is built with extensibility in mind so that you can easily integrate it with your continuous integration servers and build systems.

Finally, in this example, I will demonstrate how to use TestRail for reporting results. TestRail is a web-based test case management tool. Geocene is not particularly attached to any particular reporting tool, but TestRail is used by many testers, developers, and team leads to manage, track, and organize software testing efforts.

What is behind Test-Automation?

The goal behind this project is to Run thousands of Postman tests automatically on a daily schedule and reports all the results in TestRail. This could be useful for a QA team to monitor milestones and test plan progress and to inform the backend team about any failing tests.

Chart showing thousands of postman tests

Chart showing detail on each test run

TestRail Configuration

I’ll be using Newman to report results to TestRail, but first we need to programmatically access TestRail. I’ll use the TestRail API, which allows us to integrate automated testing frameworks, submit test results, and automate various other aspects of TestRail reporting via HTTP requests.

Go to TestRail’s Administration > Site Settings > API and click on Enable API.

Screenshot showing where to enable API

Then create a new api key:

On your Profile > My Settings > API KEYS tab and generate a new API key.

Screenshot showing how to set an API key

Linking Postman API Tests with TestRail Test Cases

I assume here that you already know how to use TestRail, and you have created some test cases.

The rules to associate your Postman tests with you TestRail test cases are simple: in Postman, just prefix test assertions with TestRail Case ID including the letter C.

Example of inserting TestRail Case ID including the letter C

This linking method can work in different ways, you can assign one TestRail test case to many Postman tests, and you can even assign many Test Rail test cases to one Postman test.

Newman Configuration

To execute Postman tests, we need to use the command-line Collection Runner Newman, but to report the results to TestRail we need also newman-reporter-testrail.

RUN npm install -g newman
RUN npm install -g newman-reporter-testrail

Once Newman and newman-reporter-testrail are installed, all we need is to export the following environment variables, here is a full example:

TESTRAIL_DOMAIN=example.testrail.io  # your own custom TestRail subdomain
TESTRAIL_USERNAME=john.doe@example.com # username, usually your email address.
TESTRAIL_APIKEY=your_api_key # This is the API key you have previously created.
TESTRAIL_PROJECTID=1
TESTRAIL_RUNID=1 
TESTRAIL_TITLE="My Postman API Tests" 
newman run collection.json --reporters cli,testrail

Test-Automation

At this point, we know how to create Test Cases, TestRuns, and how to use Newman to execute tests and report results in TestRail.

But Imagine you have a big Postman collection with 20 folders, and each folder contains hundreds if not thousands of tests, and you have a QA team that works together on the same collection from their local machine. Geocene has been in this situation several times! Here are are some blockers and questions that we have faced in the past:

  • How to deal with speed and performance?
  • The QA team has to spend several hours to run the whole Postman collection.
  • What about TestRail? Can TestRail’s API handle thousands of test results?
  • newman run collection.json --reporters cli,testrail requires using a JSON export of the collection that might change and be different from QA team member to team member.
  • sharing a public link to the collection might be an option, but this has some disadvantages
    • each time the collection changes, you have to manually sync it.
    • sharing a public link has securities issues.

The details matter, and your implementation might depend on your project, QA team practices, how Postman tests are written by the QA team, and the number of test scenarios you are trying to report with TestRail.

  • We have noticed that TestRail can crash unexpectedly when the test results get bigger (tens of thousands of test cases).
  • If the prefix rules described earlier are malformed in one of your assertions, TestRail’s API may crash. As the Postman collection gets bigger, expect to encounter more issues.
  • From a security perspective, the JSON export of the Postman collection could contain an internal token to your backend, so using a collection.json file or even a public link in your test-automation repo is risky.

To address almost all of these questions, you can use a Testrail/Postman structure that we developed that uses an Amazon ECS task that runs inside of VPC. For additional security I’ll demonstrate how to use only API Keys for both Postman and Testrail.

Postman and TestRail Structure

To solve most of the issues we have encountered with integrating TestRail, we created a TestRun for each subfolder of our Postman collection. Let’s call these “modules.” Let’s say that each module corresponds to a feature of our backend. So instead of running Newman against the whole collection and taking the risk of failure, we run Newman on each module as a separate TestRun. This structure dramatically reduces TestRail API failure rates and increases the performance of testing automation by allowing us to parallelize by the number of subfolders/modules.

subfolder structure example

Example of how the folder structure correlates in TestRail

The final script looks like this :

run_newman.sh

#!/bin/bash


COLLECTION_ID=$(curl --location --request GET "https://api.getpostman.com/collections?apikey=${POSTMAN_API_KEY}" | \
jq --arg COLLECTION_NAME "$COLLECTION_NAME" '.collections[] | select(.name==$COLLECTION_NAME) | .uid')

ENVIRONMENT_ID=$(curl --location --request GET "https://api.getpostman.com/environments?apikey=${POSTMAN_API_KEY}" | \
jq --arg ENVIRONMENT_NAME "$ENVIRONMENT_NAME" '.environments[] | select(.name==$ENVIRONMENT_NAME) | .uid')

COLLECTION_ID=${COLLECTION_ID%\"}
COLLECTION_ID="${COLLECTION_ID#\"}"
ENVIRONMENT_ID=${ENVIRONMENT_ID%\"}
ENVIRONMENT_ID="${ENVIRONMENT_ID#\"}"

curl --location --request GET "https://api.getpostman.com/collections/$COLLECTION_ID?apikey=${POSTMAN_API_KEY}" \
>> collection.json

curl --location --request GET "https://api.getpostman.com/environments/$ENVIRONMENT_ID?apikey=${POSTMAN_API_KEY}" \
>> postman_environment_variables.json

#report Newman results to Testrail by chunks (Newman runs tests on folders and updates the existing test runs)

curl -H "Content-Type: application/json" -u "${TESTRAIL_USERNAME}:${TESTRAIL_PASSWORD}" \
 "https://${TESTRAIL_DOMAIN}/index.php?/api/v2/get_runs/${TESTRAIL_PROJECTID}" >> test_runs.json

jq -c '.[]' test_runs.json | while read i; do

  TEST_RUN_NAME=$(echo $i | jq '.name')
  TEST_RUN_ID=$(echo $i | jq '.id')

  echo "start processing ${TEST_RUN_NAME}"

  newman run collection.json \
  --folder "$TEST_RUN_NAME" -e postman_environment_variables.json \
  --reporters cli,testrail \
  --timeout-request 30000 \
  --timeout-script 30000

  echo "processing ${TEST_RUN_NAME} finished"


done

Pulling Everything Together with AWS ECS Fargate

Now we are able to execute thousands of tests created with Postman, and we have a full visibility of results in TestRail. Now let’s keep all the reporting fresh and relevant by scheduling the testing automation on AWS.

Let’s say that we want our project, test-automation, to start an ECS task that runs Newman commands in a Docker container and execute tests created with Postman. Then we want to report results in TestRail using newman-reporter-testrail dependency. We use CodeBuild as well to build our Docker image and automate the release each time the test automation repository is updated.

Automation process flow

First, we need to Dockerize test-automation. Postman has its own official Docker Image on Dockerhub.

Our Dockerfile looks like this :

Dockefile

FROM node:12

RUN npm install -g newman
RUN npm install -g newman-reporter-testrail

# install jq to parse json within bash scripts
RUN curl -o /usr/local/bin/jq http://stedolan.github.io/jq/download/linux64/jq && \
  chmod +x /usr/local/bin/jq

COPY * /
RUN chmod +x /run_newman.sh

ENTRYPOINT ["/run_newman.sh"]

We use a basic buildspec.yml file that contains the commands that CodeBuild runs to build the Docker image.

buildspec.yml

version: 0.2

phases:
  pre_build:
    commands:
      - $(aws ecr get-login --no-include-email)
  build:
    commands:
      - docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION .
  post_build:
    commands:
      # push
      - docker tag $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest

Scheduling an Amazon ECS Task to Run the test-automation Container

AWS Fargate supports the ability to run tasks on a schedule and in response to CloudWatch Events. This makes it easier to launch and stop container services that you need to run only at certain times.

All we need to do now is to run our repo on a scheduled task using AWS Fargate and Cloudwatch.

As a reminder, the structure of test-automation looks like this :

test-automation
  |- buildspec.yml
  |- Dockerfile
  |- run_newman.sh

Here is a minimal example of how to set up in Terraform an ECS scheduled task using Cloudwatch events. This example is just for demonstration, so I’m ignoring details like CodePipeline, CodeBuild, ECR, IAM, and other infrastructure code. These these details are out of the scope of this post, but you can find all the infrastructure in this repository to reproduce the same solution on your AWS account.

resource "aws_ecs_task_definition" "test-automation-scheduler" {
  family                   = "test-automation"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = 1024
  memory                   = 8192
  execution_role_arn = aws_iam_role.ecs-task-execution-role.arn
  task_role_arn      = aws_iam_role.ecs-test-automation-task-role.arn
  container_definitions = <<DEFINITION
[
  {
    "image": "${aws_ecr_repository.test-automation.repository_url}:latest",
    "name": "test-automation-scheduler",
    "networkMode": "awsvpc",
    "environment": [
        {
          "name": "TESTRAIL_DOMAIN",
          "value": "exampletestrail.testrail.io"
        },
        {
          "name": "TESTRAIL_USERNAME",
          "value": "name@example.com"
        },
        {
          "name": "TESTRAIL_PASSWORD",
          "value": "secret"
        },
        {
          "name": "TESTRAIL_APIKEY",
          "value": "TestRail Api Key"
        },
        {
          "name": "TESTRAIL_PROJECTID",
          "value": "1"
        },
        {
          "name": "NODE_OPTIONS",
          "value": "--max-old-space-size=8192"
        },
        {
          "name": "TESTRAIL_INCLUDEALL",
          "value": "false"
        },
        {
          "name": "POSTMAN_API_KEY",
          "value": "Postman Api Key"
        },
        {
          "name": "COLLECTION_NAME",
          "value": "Collection Name"
        },
        {
          "name": "ENVIRONMENT_NAME",
          "value": "Postman Environment name"
        }        
    ],
    "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
            "awslogs-group": "${aws_cloudwatch_log_group.test-automation-scheduler.name}",
            "awslogs-region": "${var.AWS_REGION}",
            "awslogs-stream-prefix": "ecs"
        }
    }

  }
]
DEFINITION
}

## Cloudwatch event role

resource "aws_iam_role" "scheduled_task_cloudwatch" {
  name               = "test-automation-st-cloudwatch-role"
  assume_role_policy = file("${path.module}/policies/scheduled-task-cloudwatch-assume-role-policy.json")
}

data "template_file" "scheduled_task_cloudwatch_policy" {
  template = "${file("${path.module}/policies/scheduled-task-cloudwatch-policy.json")}"

  vars = {
    task_execution_role_arn = aws_iam_role.scheduled_task_cloudwatch.arn
  }
}

resource "aws_iam_role_policy" "scheduled_task_cloudwatch_policy" {
  name   = "test-automation-st-cloudwatch-policy"
  role   = aws_iam_role.scheduled_task_cloudwatch.id
  policy = data.template_file.scheduled_task_cloudwatch_policy.rendered
}

resource "aws_cloudwatch_log_group" "test-automation-scheduler" {
  name = "/ecs/test-automation"
}

resource "aws_cloudwatch_event_rule" "test-automation-rule" {
  name        = "test-automation"
  description = "Run every day at 5am"
  schedule_expression = "cron(0 5 * * ? *)"
}

resource "aws_ecs_cluster" "test-automation" {
  name = "test-automation"
}

resource "aws_cloudwatch_event_target" "test-automation-target" {
  target_id = "test-automation-scheduler"
  arn  =      aws_ecs_cluster.test-automation.arn
  rule      = aws_cloudwatch_event_rule.test-automation-rule.name
  role_arn  = aws_iam_role.scheduled_task_cloudwatch.arn

  ecs_target {
    task_count          = 1
    task_definition_arn = aws_ecs_task_definition.test-automation-scheduler.arn
    launch_type         = "FARGATE"

 network_configuration {
    subnets          = slice(module.vpc.public_subnets, 1, 2)
    security_groups  = [aws_security_group.ecs-demo.id]
    assign_public_ip = true
  }

  }
}

# security group
resource "aws_security_group" "ecs-demo" {
  name        = "ECS demo"
  vpc_id      = module.vpc.vpc_id
  description = "ECS demo"

  ingress {
    from_port   = 3000
    to_port     = 3000
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port   = 0
    protocol  = "-1"
    cidr_blocks = [
      "0.0.0.0/0"
    ]
  }
}

Conclusion

So, we have looked at how to set up, develop, and Dockerize automated API testing with Postman and TestRail, then deploy it on AWS with Terraform. Feel free to check out the full implementation in Geocene’s test-automation demonstration repo. I hope you enjoyed this and found some valuable insights. If you have more questions, need help automating your own testing system, or just want to drop me a line, contact Geocene here.