Are you a QA tester eager to dive into performance testing without the need for extensive programming expertise? In this article, we'll explore an approachable way for non-programmers to conduct sort of load testing on cloud apps APIs using Python. Load testing without the need for complex coding - discover how even regular QA testers can use Python to find serious bugs and uncover potential performance bottlenecks.
Performance testing is a critical aspect of ensuring your applications can handle real-world demands. I will try to explain my approach and Python scripts designed for load testing a cloud service managing browsers.
Load Testing Scenario Imagine a cloud service responsible for managing browser profiles (browsers for web scrapping). Users interact via API with the service to create, start, stop, delete, etc profiles. My Python script simulates this scenario, applying load to the cloud service by repeatedly performing these actions.
# Dependencies
import asyncio
import httpx
# Configuration
API_HOST = 'https://cloud.io'
API_KEY = 'qatest'
API_HEADERS = {
"x-cloud-api-token": API_KEY,
"Content-Type": "application/json"
}
CYCLES_COUNT = 3
# Browser profile configuration
data_start = {
"proxy": "http://127.0.0.1:8080",
"browser_settings": {"inactive_kill_timeout": 120}
}
get_profiles
function retrieves existing browser profiles from the service, simulating a scenario where users request information.async def get_profiles(cl: httpx.AsyncClient):
resp = await cl.get(f'{API_HOST}/profiles', params={'page_len': 10, 'page': 0}, headers=API_HEADERS)
return resp.json()
async def start_profile(cl: httpx.AsyncClient, uuid):
resp = await cl.post(f'{API_HOST}/profiles/{id}/start', json=data_start, headers=API_HEADERS)
if error := resp.json().get('error'):
print(f'Profile {id} not started with error {error}')
async def stop_profile(cl: httpx.AsyncClient, uuid):
resp = await cl.post(f'{API_HOST}/profiles/{id}/stop', headers=API_HEADERS)
if error := resp.json().get('error'):
print(f'Profile {id} not stopped with error {error}')
async def delete_profile(cl: httpx.AsyncClient, uuid):
resp = await cl.delete(f'{API_HOST}/profiles/{id}', headers=API_HEADERS)
if error := resp.json().get('error'):
print(f'Profile {id} not stopped with error {error}')
for conn in cl._transport._pool.connections:
if conn._connection._state.value != 1:
continue
print(f'Connection in progress: {conn}')
The main function orchestrates the load testing cycles, iterating through profiles and executing asynchronous tasks. Each cycle represents a simulated user interaction, creating, using, and deleting browser profiles.
async def main():
async with httpx.AsyncClient(timeout=httpx.Timeout(timeout=300)) as cl:
for _ in range(CYCLES_COUNT):
profiles = await get_profiles(cl)
start_tasks = [asyncio.create_task(start_profile(cl, profile['id'])) for profile in profiles]
await asyncio.gather(*start_tasks)
active_browsers = await get_active_profiles(cl)
stop_tasks = [asyncio.create_task(stop_profile(cl, active_browser['id'])) for active_browser in active_browsers['data']]
await asyncio.gather(*stop_tasks)
profiles = await get_profiles(cl)
del_tasks = [asyncio.create_task(delete_profile(cl, profile['id'])) for profile in profiles]
await asyncio.gather(*del_tasks)
# Monitor active connections for insights into load impact
This script shows a foundation for QAs to tailor load-testing scenarios to their applications. By customizing the number of cycles, adjusting user interactions, and modifying the script to fit specific API endpoints, testers can gain valuable insights into their application's performance under different loads. Here, you'll need essential monitoring tools to gain info on server states, assess the server load, and track resource utilization and logs. Utilize tools like Grafana, Kibana, Prometheus, etc, for comprehensive monitoring. Additionally, keep a close eye on the responses your script receives, ensuring a thorough evaluation of your application's performance. This approach is invaluable in your effective load testing and performance analysis.
Additionally, for a more realistic load simulation, consider opening specific pages in your browser. While I personally used a start page in my browsers, you can also explore options like Pyppeteer or Playwright to open multiple tabs and navigate through various pages. This approach enhances the authenticity of your load-testing scenario, closely resembling user interactions with your application.
# Attempt to connect to the browser using the provided profile URL
try:
browser = await connect({'browserWSEndpoint': browser_url, 'defaultViewport': None})
except Exception as e:
# Handle connection errors and print a message
print(f'Error occurred when connecting to the browser: {str(e)}')
return
# Create a new page in the connected browser
page = await browser.newPage()
# Introduce a brief delay to ensure the page is ready
await asyncio.sleep(2)
# Set the viewport dimensions for the page
width, height = 1920, 1080
await page.setViewport({'width': width, 'height': height})
# Try to navigate to a specific URL
try:
await page.goto('https://{your_website}')
# Wait for 10 seconds to simulate user interaction
await page.waitFor(10000)
# Introduce another delay for additional stability
await asyncio.sleep(5)
except pyppeteer.errors.PageError as e:
# Handle page navigation errors and print a message
print(f'Error occurred during page navigation: {str(e)}')
# Attempt to take a screenshot of the page
try:
await page.screenshot(path='screen.png', fullPage=True)
# Print a success message if the screenshot is captured successfully
print('Screenshot taken successfully.')
except Exception as e:
# Handle screenshot capture errors and print a message
print(f'Error occurred during taking a screenshot: {str(e)}')
Python's asynchronous capabilities, coupled with HTTP libraries, make it a versatile tool for load-testing cloud-based systems. This example serves as a starting point for QA engineers looking to learn Python's power in their load-testing attempts.
NOTE
In my scenario, the described script proved to be robust and impactful. It served as a useful tool in identifying and addressing numerous issues. The script's aggressive nature was okay in pinpointing critical issues, facilitating effective debugging, and leading the way for a seamless and improved user experience, which is pretty good for a QA.
In continuation, I will briefly discuss another script utilizing Python's multiprocessing module. This approach aims to enhance load generation by concurrently executing multiple instances of the testing script. The primary goal of multiprocessing is to parallelize the execution of a script, enabling simultaneous interactions with the service. This approach contrasts with the asynchronous approach discussed earlier, where tasks are executed sequentially but concurrently managed. This is more like spam/ddos with the same requests, but it also might be very useful.
def get_profiles():
response = requests.get(url=f"{api}", params=PARAMS, headers=headers)
return response
def start_profiles(list_of_profiles_uuids):
for uuid in list_of_profiles_uuids:
# ... (API request to start profile)
def stop_profiles(internal_uuids):
for uuid in internal_uuids:
# ... (API request to stop profile)
def run_script():
start_profiles(get_profile_ids())
stop_profiles(list_of_ids)
if __name__ == "__main__":
for runs in range(0, 5):
processes = []
for i in range(20):
p = multiprocessing.Process(target=run_script)
processes.append(p)
p.start()
for p in processes:
p.join()
The multiprocessing provides a strategy for load-testing applications. It allows QA engineers to experiment with different methodologies based on the unique characteristics of their applications. While asynchronous testing offers efficiency in managing concurrent tasks, multiprocessing excels in parallelizing the entire testing process. You can choose the approach that aligns best with their specific load-testing goals and application requirements.
A quick reminder:
This demo aims to introduce basic concepts in a beginner-friendly format, highlighting Python's simplicity for QA testers venturing into performance testing.
If you have programming challenges, don't hesitate to google stuff and ask colleagues, use ChatGPT or similar tools, and use GitHub Copilot for extra assistance in writing your load-testing scripts.
Also published here.