Learn Selenium - Custom Utilities & Tooling
Episode 18 of 23

Learn Selenium - Custom Utilities & Tooling

This episode covers building reusable test utilities and wrappers, custom logs, screenshots, and reporting utilities, integration with test frameworks like pytest, and sharing utilities across test suites so the test project stays concise.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

The bigger the suite, the more often you find repeating patterns: creating drivers, waiting for elements, taking screenshots, writing logs. Episode 18 covers organizing those patterns into custom utilities and tooling — code written once and used across the whole suite.

The main goal of this episode isn't just less typing, but a suite that's easier to maintain and more informative. With consistent wrappers, policy changes in one place; with centralized logging and reporting, every failure carries complete evidence.

Reusable Test Utilities and Wrappers

Wrapper for the Driver

Centralize driver creation in one wrapper that handles options, headless, and session logging:

PythonDriver wrapper in utils
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
 
 
def buat_chrome(headless=False, ukuran=None):
    opsi = Options()
    if headless:
        opsi.add_argument("--headless")
    if ukuran:
        opsi.add_argument(f"--window-size={ukuran[0]},{ukuran[1]}")
    return webdriver.Chrome(options=opsi)

buat_chrome(headless=False, ukuran=None) standardizes how the whole suite creates Chrome. An option change — for example adding an anti-crash argument — is done once.

BasePage for Shared Methods

Methods used by all pages can be pulled into a parent class:

PythonBasePage with shared methods
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
 
 
class BasePage:
    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)
 
    def klik_saat_klikable(self, locator):
        self.wait.until(EC.element_to_be_clickable(locator)).click()
 
    def baca_teks(self, locator):
        return self.driver.find_element(*locator).text

klik_saat_klikable(locator) and baca_teks(locator) become methods inherited by every page object from episode 6. This consistency guarantees every page applies the same wait policy.

Custom Logs, Screenshots, and Reporting Utilities

Centralized Logging

Logs scattered across many files are hard to trace. Centralize them via the logging module:

PythonCentralized logging configuration
import logging
 
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
log = logging.getLogger("selenium-tests")
 
 
def cek_halaman(driver, url):
    log.info("Membuka %s", url)
    driver.get(url)
    log.info("Judul: %s", driver.title)

logging.getLogger("selenium-tests") gives one logger used by the whole suite. With log.info(...) at key points, the test's step order can be reconstructed from the log when a failure occurs.

Centralized Screenshot Utility

The screenshots from episode 7 can be wrapped into a utility:

PythonScreenshot utility
from pathlib import Path
 
 
def simpan_bukti(driver, nama, folder="artifacts"):
    Path(folder).mkdir(exist_ok=True)
    path_png = Path(folder) / f"{nama}.png"
    driver.save_screenshot(str(path_png))
    return path_png

simpan_bukti(driver, nama) standardizes where and how evidence is named and stored. Used together with the pytest failure hook, it ensures every run produces consistent artifacts.

Integration with the Test Framework

Shared Fixtures in conftest.py

pytest integrates utilities through fixtures in conftest.py — this is where the driver wrapper and logger come together:

PythonDriver fixture with logging
import pytest
import logging
from utils.driver_factory import buat_chrome
 
 
@pytest.fixture
def driver():
    log = logging.getLogger("selenium-tests")
    log.info("Membuat sesi Chrome")
    d = buat_chrome(headless=True)
    yield d
    d.quit()
    log.info("Sesi ditutup")

This driver fixture provides a clean session to every test, complete with log entries for creation and closing. The whole suite uses the same fixture — setup and teardown quality is guaranteed in one place.

Reporting Hook

Add a hook to enrich reports with automatic evidence:

PythonFailure report hook
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()
    if report.when == "call" and report.failed:
        log = logging.getLogger("selenium-tests")
        log.error("Test gagal: %s", item.name)
        log.error("Detail: %s", report.longrepr)

This hook writes the failure reason to the log at error level. The combination of the driver fixture, logger, and this hook produces a suite where every failure comes with context.

Sharing Utilities Across Test Suites

A Portable Utils Package

Good utilities are usable in other projects. The only way to achieve that is writing them without dependencies on specific pages or tests:

  • driver_factory.py — only about creating drivers.
  • waits.py — only about waiting for conditions.
  • reporting.py — only about storing evidence.
Portable utils structure
utils/
├── driver_factory.py   -> driver creation
├── waits.py            -> wait conditions
├── reporting.py        -> screenshots and logs
└── __init__.py

The utils folder above never touches any page's business logic. A package like this can be published or copied to another project without major changes.

Tip

When creating utilities, write a short docstring up front and use clear names. Utilities without documentation quickly become "mystery code" that no one dares to change.

Conclusion

Episode 18 closes out the basic tooling needs of a Selenium suite: consistent driver wrappers and BasePage, centralized logging and screenshots, pytest fixtures and hooks that tie it all together, and a utils structure portable across projects.

Key takeaways:

  • Wrappers centralize driver creation and wait policy.
  • Centralized logging reconstructs test order on failure.
  • A screenshot utility standardizes failure evidence.
  • pytest fixtures and hooks integrate utilities across the suite.
  • Utils without page dependencies are easy to reuse in other projects.

In episode 19 next, we'll cover operational readiness and runbooks — runbooks for broken tests, maintenance windows, and environment drift, managing the test suite and triaging failures, recovery steps for browser compatibility issues, and test ownership and maintenance by the team.

Learn Selenium - Custom Utilities & Tooling | Learn Selenium