12

In Newman I want to test to ensure that the response code is correct, response time is reasonable and response values are correct.

In some cases, due to network hiccups or other system conditions, some requests might end up with timeouts or incorrect values that will resolve if the same request was processed a few seconds later.

in such cases, I would like to retry the exact request x times with a Y timeout between requests.

If an iteration pass after a retry, I would like the Newman exit code to be 0 (successful run).

AntonioSk
  • 528
  • 3
  • 20
OBender
  • 2,492
  • 2
  • 20
  • 33

4 Answers4

13

After few hours I had ended up with a function like this:

    function retryOnFailure(successCode, numberOfRetrys) {
        var key = request.name + '_counter';
        var execCounter = postman.getEnvironmentVariable(key) || 1;

        var sleepDuration = 1000;
        var waitUntilTime = new Date().getTime() + sleepDuration;
        if (responseCode.code !== successCode && execCounter <= numberOfRetrys) {
            while (new Date().getTime() < waitUntilTime) {
                // Do Nothing -> Wait
            }
            console.log('Retrying: ' + request.name + '\nGot: ' + responseCode.code + ' Expected: ' + successCode + '\nWaited: ' + sleepDuration / 1000 + 'sec  \nRetry Number: ' + execCounter + ' of ' + numberOfRetrys);
            execCounter++;
            postman.setEnvironmentVariable(key, execCounter);
            postman.setNextRequest(request.name);
        }
    }

Usage:

    retryOnFailure(404, 4);
JoSSte
  • 2,953
  • 6
  • 34
  • 54
OBender
  • 2,492
  • 2
  • 20
  • 33
  • yeah, a reusable function is probably the better solution here. it's tough work to get some elegant retry logic with postman/newman. I hope the implement reusable global scripts soon. – Sergej Lopatkin Apr 19 '17 at 13:47
  • where will we put this script, it it on `test script` section of our postman collection? – Chau Giang Apr 04 '19 at 11:19
  • 2
    You can place it inside the postman environment variable via pm.globals.set() and do eval() on it each time you need the function – OBender Apr 04 '19 at 15:48
7

You can setup a request workflow like this:

Create a collection with a request, then:

In the pre-request tab you can implement a counter:

// Counter for number of requests
var counter = environment.counter ? _.parseInt(environment.counter) + 1 : 1;
postman.setEnvironmentVariable("counter", counter);

Your tests tab would look like this:

const code = (responseCode.code === 200);

if (code === 200 && environment.counter < X) {
    // Stop execution
    tests["Status code is 200"] = code;
    postman.setNextRequest();
}
else {
    // retry the same request
    postman.setNextRequest("Name of this request");
}

A timeout for the request itself can be configured with the newman CLI:

newman run myCollection.json --timeout-request Y
Sergej Lopatkin
  • 1,071
  • 9
  • 11
2

Here is the reusable function for the same

postmanFunctions.common.retryOnFailure(predicate,retryCount,waitbetweenRetrySec,ReroutetorequestifNeeded ,postmanAssertions);
  • predicate function decides success or failure
  • assertion function has all postman assertion
  • if reroute is blank then after retry attempts assertions gets executed.
  • Flexible polling with retrycount and waittime(if predicate passed no more polling/reflow)
  • There is a maxflow counter(env var) which limits the number of flow jumps to avoid infinite loop

Store the below function in Globals or env:

    () => {
    var sleep = (sleepDuration) => {
        var startTime = new Date().getTime();
        while (new Date().getTime() - startTime < sleepDuration) {}
    }
    var sleepByAsyncDelayTime = () => {
        var sleepDuration = postman.getEnvironmentVariable('asyncDelayTime') || 0;
        sleep(sleepDuration);
    }
    var retryOnFailure = (predicate, numberOfRetrys, sleepDuration, reRouteRequestName, postmanAssertions) => {
        var retryCountPerReq_key = request.name + '_retry_count';
        var retryCountPerReq = pm.environment.get(retryCountPerReq_key) || 0;
        var reflowCountPerReq_key = request.name + '_reflow_count';
        var reflowCountPerReq = pm.environment.get(reflowCountPerReq_key) || 0;
        var totalReflowCount_key = 'totalReflowCount';
        var totalReflowCount = pm.environment.get(totalReflowCount_key) || 0;
        var maxReflowCounter = postman.getEnvironmentVariable('maxReflowCounter') || 0;
        var maxReflowCounterPerReq = postman.getEnvironmentVariable('maxReflowCounterPerReq') || 0;

        function clearAndExit() {
            pm.environment.unset(retryCountPerReq_key);
            pm.environment.unset(reflowCountPerReq_key);
            postmanAssertions();
        }

        function retry() {
            sleep(sleepDuration);
            pm.environment.set(retryCountPerReq_key, ++retryCountPerReq);
            postman.setNextRequest(request.name);
        }

        function reFlow() {
            if (totalReflowCount < maxReflowCounter && reflowCountPerReq < maxReflowCounterPerReq) {
                pm.environment.unset(retryCountPerReq_key);
                pm.environment.set(totalReflowCount_key, ++totalReflowCount);
                pm.environment.set(reflowCountPerReq_key, ++reflowCountPerReq);
                postman.setNextRequest(reRouteRequestName);
            } else clearAndExit();
        }
        if (predicate()) clearAndExit();
        else if (retryCountPerReq < numberOfRetrys) retry();
        else if (reRouteRequestName != '') reFlow();
        else clearAndExit();
    }
    return {
        common: {
            sleepByAsyncDelayTime,
            sleep,
            retryOnFailure
        }
    };
}
JoSSte
  • 2,953
  • 6
  • 34
  • 54
1

Here is my retry function that I define in collection pre-request script. It only works when tests are executed via collection :

    Utils = {
        wait: function (that, sleepDuration){
            that.setTimeout(() => {}, sleepDuration);
        },
        withRetry: function(that, expectedHttpStatus, maxNumberOfTries, sleepBetweenTries, businessRetryConditionCallBack, endRetryCallback){

            if (!that.pm.environment.get("collection_tries")) {
                that.pm.environment.set("collection_tries", 1);
            }

            if (((that.pm.response.code != expectedHttpStatus) || businessRetryConditionCallBack())
                && (that.pm.environment.get("collection_tries") <= maxNumberOfTries)) {
                var tries = parseInt(that.pm.environment.get("collection_tries"), 10);
                that.pm.environment.set("collection_tries", tries + 1);
                Utils.wait(that, sleepBetweenTries, maxNumberOfTries);
                that.postman.setNextRequest(that.request.name);
            } else {
                if(businessRetryConditionCallBack()){
                    // On ne passe pas à la requête suivante
                    that.postman.setNextRequest(null);
                }

                that.pm.environment.unset("collection_tries");

                endRetryCallback();
            } 
        }
    };

And here is how to use it in request on pre-request or test scripts :

    var expectedHttpStatus = 200;
    var maxNumberOfTries = 5;
    var sleepBetweenTries = 5000;

    Utils.withRetry(this, expectedHttpStatus, maxNumberOfTries, sleepBetweenTries, function(){
        // Retry business condition callback
        return pm.response.json().length <= 0;
    }, function(){
        // End retry callback
        pm.test("Has one result", function () {
            pm.expect(pm.response.json().length).to.equals(0);
        });
    });

This code will retry request as long as (http statut is different from expectedHttpStatus or businessRetryConditionCallBack is true) AND maxNumberOfTries is not reached and .

When http statut condition is true and maxNumberOfTries is reached, a check is done to verify businessRetryConditionCallBack. If not true, collection execution is stopped.

JoSSte
  • 2,953
  • 6
  • 34
  • 54
Darkerone
  • 51
  • 5