Package: locustio-backpack
Locust BackpackTheLocust Backpackis a Locustio load testing framework extension that aims to simplify writing load test scripts while also adding some functionality and awesome quirks.The main idea behind the module is "Endpoints as objects".
This means less hardcoding, fewer decision blocks, smaller chances for mistakes and bugs, less stress and more fun.Getting StartedStarting to use theBackpackis simple. After placing the module in your working directory, import it from the Locust script:import locust_backpack as BackpackAt this point, you are a proud user of the Locust Backpack. Time to put it to work.InitializationAs mentioned earlier, theBackpackideology is "Endpoints as objects".  Thus, we have to create objects from all the endpoints which will later be used in the load test.This can be done outside the TaskSet child class, in a different file, or just wherever we prefer.The master class holding the endpoint data isLocustEndpoint. Each individual endpoint has to be initialized with an instance of this class, accepting the following parameters:LocustEndpoint(self, endpoint, methods, name=None, headers=None, body=None)ArgumentDescriptionDefaultendpoint (str)string representing the actual endpointN/Amethods (str or list)string or list containing all the methods the endpoint supportsN/Aname (str)string representing a friendly or a debug name for the endpoint. This is the name the endpoint will have on the Locust UIname = endpointheaders(dict)the headers this endpoint will use{}body(dict)the body this endpoint will use{}Note: The values passed when initializing a LocustEndpoint are not permanent. These can be modified anytime during the test !Examples:Only the mandatory parameters:TeamInit = Backpack.LocustEndpoint('/public/team/v1/init', 'post')PvP2Init = Backpack.LocustEndpoint('/public/pvp2/v1/init', ['post', 'get'])Passing a custom nameResetSession = Backpack.LocustEndpoint('/public/session/v1/cheat/reset','post',
            name="/public/session/v1/cheat/reset::newUser")Passing a bodySessionStart = Backpack.LocustEndpoint('/public/session/v1/start', 'post',
            name="/public/session/v1/start::newUser", 
            body={"device_type": "test",
                    "device_id": "test", 
                    "version": 320, 
                    "language": "RO", 
                    "gender": 1, 
                    "name": "load_test", 
                    "platform": 1})Once all our LocustEndpoints are created, we need access to the TaskSet in order to be able to make requests using Locust. This can be achieved in two ways:Looping through a list of all endpoints and applying theirattach()method to the current context:for endp in [TeamInit, ResetSession, SessionStart, PvP2Init, EventInit]:
    endp.attach(self)Calling the Backpack methodUnzip()which does the same as above but saves you from providing a list of LocustEndpointsBackpack.Unzip(self)Making RequestsSimple RequestRememberself.client.post(...)? Forget about it.Locust Backpack exposes a much simpler way of making a request and tracking it's result:TeamInit.Request()orTeamInit.Request('post')CallingRequest()without a parameter will use the endpoint's passed method when initializing the LocustEndpoint. Specifying a method will use that method from the method pool.Note: IfLocustEndpointwas initialized with a list of methods andRequest()is called without arguments, aMissingMethodExceptionwill be thrown !But what happens under the hood when weLocustEndpoint.Request()?A self.client.method(*args) request is made, following the Locust style and using the default or provided methodA status code check is performed, registering the request.success or request.failure for LocustA Result object is created, containing all the request informationSimple-Chaining Dependent RequestsWhat if we want to connect multiple requests, but we want each subsequent request to be made only if the previous one succeeds? It couldn't be any easier!ResetSession + SessionStart...seriously, that's all...The above is translated to:Make a request toResetSessionCheck the result ofResetSessionIfResetSessionis successful, make a request toSessionStartOf course, any number of requests can be chained:TeamInit + ResetSession + SessionStart + EventInitLet's sayTeamInitfails in the above scenario. In such case, no request following it will be performed because the dependency chain has failed from that point.Note: Simple-Chaining has no wait time between requests. If successful, each request will be made instantly after the previous oneNote: Simple-Chaining only support LocustEndpoints that were declared with a single methodAdvanced-Chaining Dependent RequestsTo address the limitations of Simple-Chaining, Advanced-Chaining was created.
Advanced-Chaining is defined by the backpack classChain()as such:Chain(self, endpoints, independent=False, wait=0)ArgumentDescriptionDefaultendpoints (list of tuples )list of tuples containing an endpoint and it's methodN/Aindependent(bool)wether or not the provided endpoints depend on the success of the previous oneFalsewait(int)integer representing how much time to wait between making the provided requests0Example:Backpack.Chain([(PvP2Init, 'get'), (PvP2Init, 'post')], wait=2)ScenariosWhat if there was an easy way to build and run a scenario? Well there isn't one way to do it, but multiple.Simple ScenarioThe most basic way of building a scenario is the backpack'sScenario()class.Scenario(self, endpoints, wait=1, runtime=None, independent_requests=False)ArgumentDescriptionDefaultendpoints (list of tuples )list of tuples containing an endpoint and it's methodN/Await(int)integer representing how much time to wait(sec) between making the provided requests1runtime(int)integer representing the runtime(sec) of the scenarioNoneindependent_requests(bool)wether or not the provided endpoints depend on the success of the previous oneFalseExample:scenario_endpoints = [(TeamInit, 'post'), (ResetSession, 'post'),
                    (SessionStart, 'post'), (PvP2Init, 'get'),
                    (PvP2Init, 'post'), (EventInit, 'post')]SCENARIO = backpack.Scenario(scenario_endpoints, runtime=10, independent_requests=True)But this will not run the  Scenario yet! Two methods are available for running the scenario:SCENARIO.run()Or if you would like to run the scenario only once, regardless of runtime:SCENARIO.run_once()Weighted ScenarioMaybe we want to add a chance for endpoints when running a scenario.
For that purpose,WeightedScenario()comes into play. Subclassing the Simple Scenario, there are only two minor differences between them:WeightedScenario() does not have an independent_requests argumentThe tuples passed to WeightedScenario() have a 3rd element representing an int between 1-100 which is that chance for that request to be made (100 meaning 100% chance)Example:scenario_endpoints = [(TeamInit, 'post', 100), (ResetSession, 'post', 20),
                    (SessionStart, 'post', 100), (PvP2Init, 'get', 75), 
                    (PvP2Init, 'post', 15), (EventInit, 'post', 50)]

SCENARIO_2 = Backpack.WeightedScenario(scenario_endpoints, runtime=15)

SCENARIO_2.run()orSCENARIO.run_once()Sequenced Scenario (WIP)One other way of building a scenario is using theBackpack.SequenceScenario()featureWhat this scenario builder has special is it's ability to receive a list-of-lists-of-tuples or a list-of-tuples-of-lists-of-tuples.Example:First, we define some sequences. These are lists of tuples containing the LocustEndpoint, it's method and anoptionalweight/chancesequence_1 = [(TeamInit, 'post', 10), (ResetSession, 'post', 20)]
sequence_2 = [(SessionStart, 'post', 30), (PvP2Init, 'get', 20)]
sequence_3 = [(PvP2Init, 'post', 50), (EventInit, 'post', 80)]Then we build the sequenced scenario by passing the sequences above and anoptionalweight/chanceSCENARIO_3 = Backpack.SequenceScenario([sequence_1, sequence_2, sequence_3])ORSCENARIO_3 = Backpack.SequenceScenario([(sequence_1, 40), (sequence_2, 70), (sequence_3, 25)])Then we can finally call therun()method:SCENARIO_3.run()orSCENARIO_3.run_once()The Result ObjectWhenever a LocustEndpoint makes a request, aResultobject is created. Every subsequent request to that LocustEndpoint overwrites the previous Result object.This Result object holds information about the latest request to it's endpoint and has a few useful attributes as well:LocustEndpoint.Result.success       # Equals True if request is successful
LocustEndpoint.Result.failure       # Equals True if request has failed
LocustEndpoint.Result.status_code   # Equals the status code of the request
LocustEndpoint.Result.response      # Contains the bare response of the requestLocustEndpoint.Result.json()        # Tries to return the json-formatted responseDependency buildingThe Locust Backpack is a deep one, with many pockets.Let's assume we have an endpoint that needs an element from the response of another endpoint. Of course, we could parse the response and assign that specific key/element to the other endpoint, but why go through all that trouble every time?For example, let's say we have an endpointEventInitthat needs the key'player'from another endpointSessionStartWe can permanently declare this dependency anywhere in the Locust script with the following:EventInit >> DEPENDS_ON('player') >> SessionStartOnce this is done, every successful request toSessionStartwill assign it'splayerkey toEventInit's body, makingEventInituse it in future requests.Other featuresDon't think that's all. This backpack is prepared for long journeys.Sealing and UnsealingEach LocustEndpoint has sealing and unsealing features which can be activated and deactivated withEventInit.sealEventInit.unsealSealing an endpoint means that the endpoint will be unable to make any further requests until being unsealed.This does not interfere in any way with the script logic, but once a sealed endpoint request is reached, the request part is skipped and that endpoint's latest Result will be set to a silent fail (not registered anywhere)There is also a way of sealing and unsealing all LocustEndpoints at once:Backpack.SealAll()Backpack.UnsealAll()Check a Request's SuccessAside from using theResult()object to access all information regarding the latest request, including the success withEventInit.Result.success, we can use a simpler and quicker way:More specifically, we can check for thetruthnessof a LocustEndpoint instanceif EventInit: # Your codeIn other words, the following two methods have the exact same result:if EventInit.Result.success: # Codeif EventInit: # CodeMass AssignationAs already mentioned, attributes of a LocustEndpoint can be changed anytime. But what if you want to modify or create a variable for all (or more than one) LocustEndpoints?This is achieved usingBackpack.SetGlobal()ArgumentDescriptionDefaultvariable (str)the variable that will be modified or createdN/Avalue (anything)value of variableN/Aselective (list)integer representing the runtime(sec) of the scenarioAll LocustEndpointsExample:Setting the same header for all LocustEndpointsBackpack.SetGlobal('headers', auth_header)Or just for specific LocustEndpointsBackpack.SetGlobal('headers', auth_header, selective=[EventInit, TeamInit])Zipping (WIP)Every unzipped backpack should be zipped back. It would be a pitty to lose anything from it.To that effect, the Backpack functionZip()was created.CallingBackpack.Zip()from anywhere within your current test will save all the test data to a folder within your workspace.Zip() will:Check if the Locust address "http://localhost:8089/" is accessibleDownload the Locust .csv filesBuild a clone of the Locust stats pageUse seaborn to plot the test detailsNicely save everything to a datestamped folder in your current working directoryNote! Due to the fact that all zipping operations are done on a localhost URL,Zip()should only be called by the Locust MasterFlow Chart of a LocustEndpoint Requestgraph TB

A(LocustEndpoint) -- Request --> B(TaskSet.client.method)
B -- status_check --> C(Request Result)
C -- request.success --> D(LocustEndpoint.Result)
D --> F(Resolve dependencies)
C -- request.failure --> E(LocustEndpoint.Result)