100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Testing

Building an Automation Framework from Scratch

A practical blueprint for structuring a Selenium test automation framework — page objects, driver management, reporting, and parallel execution — from the ground up.

Practical SeleniumIntermediate12 min readJul 10, 2026
Analogies

Designing a Test Automation Framework from Scratch

A test automation framework is more than a folder of test scripts — it's the reusable scaffolding of driver management, page abstractions, configuration, reporting, and CI wiring that makes writing the 200th test as easy as writing the first. Building one from scratch means making deliberate decisions early about project structure, how tests obtain a browser instance, how failures are diagnosed, and how the suite scales to run in parallel across a CI pipeline, because retrofitting these decisions after 300 tests exist is far more expensive than getting them right up front.

🏏

Cricket analogy: Building a framework before writing tests is like a groundsman preparing a proper pitch before any match is played on it — skip that groundwork and every subsequent game suffers from an unreliable playing surface.

Project Structure and the Page Object Model

A typical Selenium framework separates concerns into distinct layers: a pages/ directory holding Page Object classes (one per screen or major component), a tests/ directory holding pytest/JUnit/TestNG test methods that call into those page objects, a config/ directory for environment-specific settings (base URLs, timeouts, credentials pulled from environment variables, never hard-coded), and a utils/ directory for shared helpers like custom wait conditions or data factories. Each Page Object exposes intent-revealing methods, e.g. login_page.login(username, password), rather than exposing raw find_element calls to the test layer, which keeps tests readable as executable specifications of behavior.

🏏

Cricket analogy: Separating pages/ from tests/ is like separating the groundstaff's job of maintaining the pitch from the players' job of playing on it — each role is distinct, and mixing them causes confusion about who's responsible for what.

python
# project structure
# framework/
#   config/settings.py
#   pages/base_page.py
#   pages/login_page.py
#   utils/driver_factory.py
#   tests/test_login.py

# pages/login_page.py
from selenium.webdriver.common.by import By
from pages.base_page import BasePage

class LoginPage(BasePage):
    USERNAME = (By.CSS_SELECTOR, "input[data-testid='username']")
    PASSWORD = (By.CSS_SELECTOR, "input[data-testid='password']")
    SUBMIT = (By.CSS_SELECTOR, "button[data-testid='login-submit']")

    def login(self, username: str, password: str):
        self.type_text(self.USERNAME, username)
        self.type_text(self.PASSWORD, password)
        self.click(self.SUBMIT)
        return self

# tests/test_login.py
def test_valid_login_redirects_to_dashboard(driver, base_url):
    login_page = LoginPage(driver).open(base_url + "/login")
    login_page.login("qa_user", "S3cret!")
    assert "/dashboard" in driver.current_url

Driver Management, Reporting, and CI Integration

A driver factory centralizes browser creation behind a single function that reads configuration (browser type, headless flag, remote grid URL) and returns a ready-to-use WebDriver instance, letting the same test suite run locally against Chrome and in CI against a Selenium Grid or cloud provider without any change to test code. Pairing this with pytest fixtures (or TestNG/JUnit equivalents) that inject the driver and automatically quit it after each test, plus a reporting library like Allure or pytest-html that captures a screenshot on failure, turns a red CI run into an actionable artifact rather than a bare stack trace.

🏏

Cricket analogy: A driver factory that swaps the underlying browser based on config, without changing test code, is like a squad that can field the same tactical setup on turf or on a traditional grass pitch depending on the venue, without rewriting the game plan.

Allure Report is a strong default choice for Selenium frameworks because it correlates screenshots, logs, and step-level detail directly with the failing assertion, and its trend graphs make flaky-test patterns visible over many CI runs — far more actionable than a raw pytest console log.

Test Data Management and Parallel Execution

Scaling a framework to run hundreds of tests in minutes requires parallel execution, typically via pytest-xdist, TestNG's parallel suite configuration, or a Selenium Grid/cloud provider that spins up many browser sessions concurrently; this only works safely if the earlier discipline of isolated, per-test data generation was followed, since parallel workers sharing mutable fixtures will produce nondeterministic failures. A well-designed framework also separates 'fast, deterministic' unit-style checks from 'slow, environment-dependent' end-to-end UI tests via pytest markers or TestNG groups, so a developer can run a quick smoke subset locally while the full regression suite runs in CI.

🏏

Cricket analogy: Running tests in parallel across many workers is like a franchise fielding multiple net sessions simultaneously across different practice pitches, each net independent so players don't interfere with one another's drills.

Do not parallelize a suite that still relies on shared fixtures or a single seeded database record 'to save time.' The intermittent, hard-to-reproduce failures this introduces will cost far more engineering time diagnosing false negatives than the parallelization saves in CI minutes.

  • A framework separates pages/, tests/, config/, and utils/ concerns rather than mixing locators and assertions in one file.
  • Page Objects expose intent-revealing methods (login()) rather than raw find_element calls to test code.
  • A centralized driver factory lets the same tests run locally and against remote grids via configuration alone.
  • Failure reporting (screenshots, step logs) via tools like Allure turns CI failures into actionable artifacts.
  • Safe parallel execution depends on the isolated test data discipline established from day one.
  • Marking tests as smoke vs full regression lets developers get fast local feedback while CI runs everything.
  • Framework decisions made early (structure, config, data isolation) are far cheaper than retrofitting them later.

Practice what you learned

Was this page helpful?

Topics covered

#Testing#SeleniumStudyNotes#TestingQA#BuildingAnAutomationFrameworkFromScratch#Building#Automation#Framework#Scratch#StudyNotes#SkillVeris