Code Mage LogoCode Mage
Back to Blog
Python Deep Dive

Python Type Hints: A Practical Guide for Test Engineers

Type hints aren't just for application code. Used well, they make your test helpers, fixtures, and page objects dramatically easier to maintain.

February 3, 20264 min read
Share LinkedIn X / Twitter
pythontype-hintstestingbest-practices

Type hints in Python are optional — the interpreter ignores them at runtime. That's exactly why so many test codebases skip them entirely. It's also a big mistake.

When your test suite grows to hundreds of files, the question isn't "will I remember what this function returns?" It's "will the person reading this six months from now understand it?" Type hints are documentation that your editor can actually verify.

Here's what I actually use in production.

The Basics You Actually Need

You don't need to learn every corner of the typing module. These cover 90% of test code:

# Basic types
name: str = "standard_user"
timeout: int = 5000
rate: float = 0.05
is_logged_in: bool = False

# Collections
tags: list[str] = ["smoke", "login"]
config: dict[str, str] = {"browser": "chromium"}

# Optional — the value might be None
user_id: str | None = None  # Python 3.10+
# or the older style:
from typing import Optional
user_id: Optional[str] = None

Annotating Functions — The Most Important Part

The biggest payoff comes from annotating your function signatures. Your editor can then tell you exactly what a helper returns and what it expects.

def get_auth_token(username: str, password: str) -> str:
    """Returns a bearer token for API authentication."""
    response = requests.post("/api/login", json={
        "username": username,
        "password": password
    })
    return response.json()["token"]

Now when you call get_auth_token(...), your IDE shows the return type and catches bugs before they run:

token = get_auth_token("admin", "secret123")
token.split()  # Fine — str has split()
token.keys()   # ❌ Editor warns: str has no attribute 'keys'

Page Objects with Type Hints

This is where it really pays off. A typed page object makes every single method self-documenting:

from playwright.sync_api import Page, Locator

class LoginPage:
    def __init__(self, page: Page) -> None:
        self.page = page
        self.username_input: Locator = page.locator("#user-name")
        self.password_input: Locator = page.locator("#password")
        self.login_button: Locator = page.locator("#login-button")
        self.error_message: Locator = page.locator('[data-test="error"]')

    def login(self, username: str, password: str) -> None:
        self.username_input.fill(username)
        self.password_input.fill(password)
        self.login_button.click()

    def get_error_text(self) -> str | None:
        if self.error_message.is_visible():
            return self.error_message.text_content()
        return None

When someone new reads this, get_error_text() -> str | None immediately tells them: "this might not return anything, check for None." No comments needed.

TypedDict for Test Data

Instead of throwing around plain dicts for test data, use TypedDict:

from typing import TypedDict

class UserCredentials(TypedDict):
    username: str
    password: str
    role: str

USERS: dict[str, UserCredentials] = {
    "standard": {
        "username": "standard_user",
        "password": "secret_sauce",
        "role": "buyer"
    },
    "admin": {
        "username": "admin_user",
        "password": "secret_sauce",
        "role": "admin"
    }
}

Now USERS["standard"] gives you full autocomplete on username, password, and role. No more typos in your test data keys.

Dataclasses for Complex Test Data

When TypedDict isn't enough, switch to dataclasses:

from dataclasses import dataclass, field

@dataclass
class ProductTestData:
    name: str
    price: float
    category: str
    in_stock: bool = True
    tags: list[str] = field(default_factory=list)

backpack = ProductTestData(
    name="Sauce Labs Backpack",
    price=29.99,
    category="bags"
)

Dataclasses give you __repr__ for free (which is amazing for test failure messages), type checking, and rock-solid IDE support.

Typing Fixtures in pytest

pytest fixtures can also be deeply typed:

import pytest
from playwright.sync_api import Page, Browser

@pytest.fixture
def authenticated_page(page: Page) -> Page:
    """Returns a Page instance already logged in as standard_user."""
    page.goto("/")
    page.fill("#user-name", "standard_user")
    page.fill("#password", "secret_sauce")
    page.click("#login-button")
    page.wait_for_url("**/inventory.html")
    return page

The return type -> Page tells pytest and your IDE exactly what this fixture yields, enabling proper autocomplete in every single test that uses it.

Running mypy in CI

Having type hints without checking them is like writing tests and never running them. Put mypy in your pre-commit or CI:

pip install mypy
mypy tests/ --ignore-missing-imports

Or add it to your pyproject.toml:

[tool.mypy]
python_version = "3.12"
strict = false
ignore_missing_imports = true

Start with strict = false and tighten it up gradually. Trying to type everything strictly on day one is a trap, don't do it.

What Not to Over-Type

Type hints add value when they communicate non-obvious things. Skip them when they're pure noise:

# Noise — the return type is obvious
def add(a: int, b: int) -> int:
    return a + b

# Useful — the return type isn't obvious
def parse_response(data: dict[str, object]) -> list[ProductTestData]:
    ...

Also: don't type-annotate everything inside the test functions themselves. Type the infrastructure (fixtures, helpers, page objects) and keep the test body readable.

The Payoff

On a team project, I introduced TypedDict for our test data and typed all our page object methods. Three months later a new engineer joined, set up the project, and literally said "I can actually read this codebase." That's the metric that matters.

Type hints aren't about being clever. They're about writing code that survives the test of time (and survives new team members).

Join the Mage Circle

Enjoyed this? Get more — test automation deep dives, scraping tricks, and career guides straight to your inbox.

Found this helpful?

React:
Muhammad Hammad Faisal

Muhammad Hammad Faisal

Software Engineer (Test Automation) @ Arbisoft

Software Engineer at Arbisoft building production scraping pipelines, anti-detection systems, and browser automation tooling.