Skip to main content

BorderlineData

Selenium Webdriver and Python

BLOG

How to Automate Boundary Value Testing with Selenium Webdriver and Python

Let’s face it: manually typing extreme values into a web form over and over again is a fast track to burnout. That’s exactly why we turn to automated functional testing. But if you are only writing basic Selenium scripts that input standard, happy-path data, you are completely missing out on the framework’s core power. In this guide, we will explore how to build a robust, data-driven Selenium WebDriver test in Python that systematically hammers input boundaries using an elegant, reusable framework—saving you hours of tedious QA work.

Today, we are going to look at how to structure an automated Selenium WebDriver test in Python that handles an input boundary using a neat, reusable data matrix, and then look at how to scale it for professional production pipelines.

Target Scenario

Imagine we have a registration form with an age input field. The business rules state that users must be between 18 and 65 years old to register.

To thoroughly test this boundary using Boundary Value Analysis (BVA), we want to automate checks for five critical data points:

  • 17 (invalid below minimum)

  • 18 (valid exact minimum)

  • 40 (valid mid-point)

  • 65 (valid exact maximum)

  • 66 (invalid above maximum)

Baseline Code Implementation

Instead of copy-pasting your element selectors five separate times, we can decouple our test data from our test execution. We store our boundary data in a list of dictionaries, pairing each input value with its expected outcome.

Selenium

 
from selenium import webdriver
from selenium.webdriver.common.by import By
import time

driver = webdriver.Chrome()
driver.get("https://example-testing-form.com/register")

# The Boundary Test Data Matrix
boundary_matrix = [
    {"age": "17", "expected_valid": False, "desc": "Below absolute minimum"},
    {"age": "18", "expected_valid": True,  "desc": "Exact minimum boundary"},
    {"age": "40", "expected_valid": True,  "desc": "Happy middle value"},
    {"age": "65", "expected_valid": True,  "desc": "Exact maximum boundary"},
    {"age": "66", "expected_valid": False, "desc": "Above absolute maximum"}
]

for test in boundary_matrix:
    age_input = driver.find_element(By.ID, "age-field")
    submit_btn = driver.find_element(By.ID, "submit-button")
    
    age_input.clear()
    age_input.send_keys(test["age"])
    submit_btn.click()
    
    time.sleep(0.5) # Quick pause for UI state update
    
    if test["expected_valid"]:
        # Verify no error message pops up
        errors = driver.find_elements(By.CLASS_NAME, "error-msg")
        assert len(errors) == 0, f"Failed on {test['desc']}: Expected success but got an error."
    else:
        # Verify an error message is displayed
        error_text = driver.find_element(By.CLASS_NAME, "error-msg").text
        assert "Invalid age range" in error_text, f"Failed on {test['desc']}: Expected error boundary check."

driver.quit()

 

Advertisement

Why a Basic Matrix Wins

By separating your test logic from your test data matrix, you can update your boundary constraints in one single place. If marketing suddenly decides that 16-year-olds can join, you simply update your matrix array, hit run, and let Selenium do the heavy lifting.

Hidden Trap: Why This Script Breaks in Production

If you share the script above with a senior automation engineer, they will point out two massive issues that prevent it from working reliably in a CI/CD pipeline:

  1. The Flakiness Factor (time.sleep): Relying on time.sleep(0.5) is a cardinal sin in Selenium. If the network hiccups and the UI takes 0.6 seconds to render, your test fails. If the UI loads in 0.05 seconds, you waste valuable time.

  2. The “All-or-Nothing” Failure Cascades: Because the matrix runs inside a standard Python for loop, if the first boundary value (17) fails its assertion, the script crashes immediately. Values 18 through 66 are never evaluated, and driver.quit() is skipped entirely—leaving a “zombie” Chrome process running in the background.

Pro Upgrade: Data-Driven Pytest + Explicit Waits

To turn this into a production-grade, bulletproof test script, we need to leverage two critical tools: Pytest Parameterization and Selenium Explicit Waits.

By moving our matrix into pytest.mark.parametrize, Pytest treats every single boundary condition as a completely independent test case. If one boundary fails, the others keep running, and your browser teardown always triggers smoothly.

Python

 
import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# A clean fixture to handle browser lifecycle
@pytest.fixture
def driver():
    driver = webdriver.Chrome()
    driver.get("https://example-testing-form.com/register")
    yield driver
    driver.quit()  # Guaranteed cleanup, even if an assertion fails

# The Pro-Grade Parameterized Data Matrix
@pytest.mark.parametrize("age, expected_valid, desc", [
    ("17", False, "Below absolute minimum"),
    ("18", True,  "Exact minimum boundary"),
    ("40", True,  "Happy middle value"),
    ("65", True,  "Exact maximum boundary"),
    ("66", False, "Above absolute maximum")
])
def test_age_input_boundaries(driver, age, expected_valid, desc):
    # Dynamic 10-second wait instead of hardcoded sleep
    wait = WebDriverWait(driver, 10)
    
    # Wait until the input is ready
    age_input = wait.until(EC.presence_of_element_located((By.ID, "age-field")))
    submit_btn = driver.find_element(By.ID, "submit-button")
    
    age_input.clear()
    age_input.send_keys(age)
    submit_btn.click()
    
    if expected_valid:
        # Dynamic check ensuring no error elements display
        errors = driver.find_elements(By.CLASS_NAME, "error-msg")
        assert len(errors) == 0, f"Failed on {desc}: Expected success but got an error."
    else:
        # Explicitly wait for the error message to dynamically render
        error_element = wait.until(EC.presence_of_element_located((By.CLASS_NAME, "error-msg")))
        assert "Invalid age range" in error_element.text, f"Failed on {desc}: Expected validation message."

 

Architecture Breakdown: Why the Pro Approach Scales

By evolving your script into a framework-driven layout, you realize several immediate advantages:

FeatureBaseline ScriptPro-Level Pytest Script
Execution ModelingMonolithic Loop (Fails early)Isolated Tests (Runs every boundary)
Timing StrategyHardcoded time.sleep() (Flaky)Dynamic WebDriverWait (Fast & stable)
Reporting Output1 Generic pass/fail5 Granular result logs
Resource SafetyRisk of zombie browser processesGuaranteed driver.quit() execution

 

PRO TIP

When you write boundary automation this way, your test logs read like documentation. If a developer accidentally shifts a validation rule in the source code from <= to <, your Pytest reporting dashboard will pinpoint the exact boundary boundary edge that cracked within milliseconds.

Recommended Books

Test-Driven Development with Python: Obey the Testing Goat: Using Django, Selenium, and JavaScript Harry Percival BROWSE
Hands-On Selenium WebDriver with Java: A Deep Dive into the Development of End-to-End Tests Boni Garcia BROWSE