After getting familiar with Locust, it’s time for me to try to write the real test. The project to be tested is using ADFS authentication. So if an unauthenticated user visited its URL https://project.mycompany.com, it will be redirected to:
https://fs.mycompany.com/adfs/ls/?wtrealm=https%3A%2F%2Fproject.mycompany.com&wctx=WsFedOwinState%3__SUPER_LONG_BASE64_TEXT__&wa=wsignin1.0
Which prompts the username and password:
Once I entered the correct username and password, the project’s landing page is shown.
To help me to know the submission sequence, I logout, open Chrome’s DevTools, then repeat the login process. Here’s what I see on DevTools -> Network -> Doc tab:
We can see that there was 3 redirects (302):
- When we load https://project.mycompany.com
- When we submit the username and password (POST)
- POST to https://project.mycompany.com
First, let’s observe the form of the login page:
<form method="post" id="loginForm" autocomplete="off" novalidate="novalidate" onkeypress="if (event && event.keyCode == 13) Login.submitLoginRequest();" action="/adfs/ls/?wtrealm=https%3A%2F%2Fproject.mycompany.com&wctx=WsFedOwinState%3D__LONG_BASE64_TEXT__&wa=wsignin1.0&client-request-id=__GUID_VALUE__"> <div id="error" class="fieldMargin error smallText" style="display: none;"> <span id="errorText" for=""></span> </div> <div id="formsAuthenticationArea"> <div id="userNameArea"> <label id="userNameInputLabel" for="userNameInput" class="hidden">User Account</label> <input id="userNameInput" name="UserName" type="email" value="" tabindex="1" class="text fullWidth" spellcheck="false" placeholder="someone@example.com" autocomplete="off"> </div> <div id="passwordArea"> <label id="passwordInputLabel" for="passwordInput" class="hidden">Password</label> <input id="passwordInput" name="Password" type="password" tabindex="2" class="text fullWidth" placeholder="Password" autocomplete="off"> </div> <div id="kmsiArea" style="display:''"> <input type="checkbox" name="Kmsi" id="kmsiInput" value="true" tabindex="3"> <label for="kmsiInput">Keep me signed in</label> </div> <div id="submissionArea" class="submitMargin"> <span id="submitButton" class="submit" tabindex="4" role="button" onkeypress="if (event && event.keyCode == 32) Login.submitLoginRequest();" onclick="return Login.submitLoginRequest();">Sign in</span> </div> </div> <input id="optionForms" type="hidden" name="AuthMethod" value="FormsAuthentication"> </form>
From above, we can confirm that we need to extract out the form’s action value.
Looking at the first POST (submit username and password), we can confirm the parameters submitted.
After submit, it redirected to a page:
<html> <head> <title>Working...</title> </head> <body> <form method="POST" name="hiddenform" action="https://project.mycompany.com:443/"> <input type="hidden" name="wa" value="wsignin1.0"/> <input type="hidden" name="wresult" value="<t:RequestSecurityTokenResponse ....VERY LONG ENCODED XML... </t:RequestSecurityTokenResponse>"/> <input type="hidden" name="wctx" value="WsFedOwinState=...QUITE LONG BASE64 TEXT ..."/> <noscript> <p>Script is disabled. Click Submit to continue.</p> <input type="submit" value="Submit"/> </noscript> </form> <script language="javascript">window.setTimeout('document.forms[0].submit()', 0);</script> </body> </html>
We can see above, it is an auto-submit form where the ADFS authentication result is submitted.
Base on the sequence above, here’s how the construct of our test:
from locust import HttpUser, between, task import logging #to parse HTML from bs4 import BeautifulSoup class WebsiteUser(HttpUser): host = "https://project.mycompany.com" #wait for 3 to 10 seconds before each request wait_time = between(3,10) def on_start(self): resp = self.client.get("/", allow_redirects=True) soup = BeautifulSoup(resp.text, "html.parser") logging.info(soup.title) #steps to handle unauthenticated users if soup.title.contents[0] == "Sign In": # find the login form's action logging.info('Trying to login') action = soup.form["action"] url = "https://fs.mycompany.com" + action logging.info("Post URL: " + url) #submit login information loginResp = self.client.post(url, {"UserName":"__USERNAME__", "Password":"__PASSWORD__", "AuthMethod": "FormsAuthentication"}, allow_redirects=True) logging.info("Login Result URL: " + loginResp.url) #parse the login submit result soupLogin = BeautifulSoup(loginResp.text, "html.parser") #construct the auto-submit form inputs = soupLogin.find_all("input") dict = {} for inp in inputs: if inp.get("name") != None: dict[inp["name"]] = inp["value"] # POST auto-submit form resp = self.client.post("/", dict, allow_redirects=True) soup = BeautifulSoup(resp.text, "html.parser") # the title should be project's landing page title logging.info(soup.title) @task def index(self): resp = self.client.get("/", allow_redirects=True) soup = BeautifulSoup(resp.text, "html.parser") logging.info("URL: " + resp.url) logging.info(soup.title)
With that, we should be able to write more @task to test the performance of each pages. Hopefully, we can identify the choke-points before opening access to the users.
That’s all friends! I hope it helps. Cheers!