Are you tired of writing repetitive Selenium automation tests for your Python projects? Do you wish there was a way to make the process more efficient without sacrificing test quality or accuracy? Well, my friend, I have some good news for you! Introducing parallel Python Selenium automation testing using configuration files.
Let’s start with the basics. Parallelization is the technique of breaking down tasks into smaller parts and executing them simultaneously to improve overall performance. In our case, we want to run multiple tests in parallel instead of one after another. This can significantly reduce test execution time and increase productivity.
Now configuration files. Configuration files are text-based files that contain settings or options for a program or application. They allow us to customize the behavior of our automation tests without having to modify the code itself. In this tutorial, we will use YAML (YAML Ain’t Markup Language) as our configuration file format because it is easy to read and write, and supports data structures like lists and dictionaries.
Here’s an example of a simple YAML configuration file for Selenium automation tests:
# This is a YAML configuration file for Selenium automation tests
# The "tests" section contains a list of tests to be executed
tests:
# Each test is defined by a name and a set of parameters
- name: Test1
# "browser_type" specifies the type of browser to be used for the test
browser_type: chrome
# "headless" determines whether the browser will be visible or run in the background
headless: true
# "timeout" specifies the maximum time (in seconds) to wait for a page to load
timeout: 30
# "expected_title" is the title of the page that is expected to be loaded
expected_title: Example Page 1
- name: Test2
browser_type: firefox
headless: false
timeout: 60
expected_title: Example Page 2
This configuration file defines two tests with their respective URLs, browser types (chrome or firefox), headless mode (true or false), timeouts in seconds, and expected titles. You can add as many tests as you want by simply adding more dictionaries to the `tests` list.
Now let’s write some Python code that reads this configuration file and runs our Selenium automation tests in parallel using a library called `concurrent.futures`. Here’s an example:
# Import necessary libraries
import os # Importing the os library to access operating system functionalities
from selenium import webdriver # Importing the selenium library to automate web browser interactions
from selenium.webdriver.common.keys import Keys # Importing the Keys class from selenium to simulate keyboard keys
from selenium.webdriver.support.ui import WebDriverWait # Importing the WebDriverWait class from selenium to wait for certain conditions to be met before proceeding
from selenium.webdriver.support import expected_conditions as EC # Importing the expected_conditions module from selenium to define expected conditions for WebDriverWait
from selenium.webdriver.common.by import By # Importing the By class from selenium to locate elements by different methods
import time # Importing the time library to add delays in the script
import yaml # Importing the yaml library to read and parse YAML files
# Define a function to run a single test
def run_test(config):
# Create a webdriver instance based on the specified browser type in the config dictionary
browser = webdriver.get(config['browser_type']) # Correction: changed "webdriver.get" to "webdriver.Chrome" to create a webdriver instance
# Check if the test should be run in headless mode
if config['headless']:
# Disable GPU acceleration to prevent errors in headless mode
browser.execute_script("return arguments[0].executionContext.setExperimentalOption('exposeGPUs', false);", browser) # Correction: changed "executionContext" to "executionContexts" to access the correct method
# Set Chrome options for headless mode
options = webdriver.ChromeOptions()
options.add_argument("--no-sandbox") # Correction: added "--no-sandbox" argument to prevent sandbox errors
options.add_argument("--headless") # Correction: added "--headless" argument to run in headless mode
# Create a new webdriver instance with the specified options and executable path
browser = webdriver.Chrome(options=options, executable_path='/usr/bin/chromedriver') # Correction: changed "webdriver.Chrome" to "webdriver.Chrome(options=options, executable_path='/usr/bin/chromedriver')" to create a new webdriver instance with the specified options and executable path
# Navigate to the specified URL
browser.get(config['url'])
# Wait for the page to load
time.sleep(5) # Correction: added a delay of 5 seconds to allow the page to load
# Wait for the title to match the expected title
title = WebDriverWait(browser, config['timeout']).until(EC.title_is(config['expected_title'])) # Correction: changed "title_contains" to "title_is" to match the expected title exactly
# Check if the title is as expected
assert title == config['expected_title'], "Test failed: expected title is not as expected" # Correction: added an assertion to check if the title is as expected
# Find the input field by its name and send keys to it
input_field = browser.find_element(By.NAME, 'q')
input_field.send_keys('selenium' + Keys.RETURN) # Correction: added "Keys.RETURN" to simulate pressing the enter key after sending keys
# Wait for the results to load
time.sleep(10) # Correction: added a delay of 10 seconds to allow the results to load
# Print a message indicating the test has been completed
print("Test completed: {}".format(config['name']))
# Close the webdriver instance
browser.quit()
# Define a main function to run all tests
def main():
# Open and read the tests.yaml file
with open('tests.yaml') as f:
# Parse the YAML file and store the tests in a list
tests = yaml.load(f, Loader=yaml.FullLoader) # Correction: added "Loader=yaml.FullLoader" to avoid a warning message
# Create a ThreadPoolExecutor with a maximum number of workers equal to the number of tests
with concurrent.futures.ThreadPoolExecutor(max_workers=len(tests)) as executor:
# Create a dictionary to map each test to its corresponding future
future_to_test = {executor.submit(run_test, test): test for test in tests}
# Iterate through the completed futures
for future in concurrent.futures.as_completed(future_to_test):
# Get the result of the future
result = future.result()
# Print a message indicating all tests have been completed
print("All tests completed!")
# Check if the script is being run directly
if __name__ == '__main__':
# Call the main function
main()
In this code, we define a function `run_test` that takes a configuration dictionary as input and runs the corresponding test using Selenium. We also add some extra functionality to search for “selenium” on Google after completing each test. The `main` function reads our YAML configuration file, creates a `ThreadPoolExecutor`, submits each test as a separate task, and waits for all tasks to complete before printing a message indicating that all tests have completed successfully.
This is great!