Introduction
Automation testing isn’t just about writing code—it’s about strategy, architecture, and experience. Many beginners dive straight into scripting tests but stumble over "small" mistakes that later snowball into major issues: flaky tests, maintenance nightmares, or confusing reports.
This article compiles the most frequent pitfalls new automation engineers face. The goal isn’t to shame but to educate: why these mistakes hurt and how to sidestep them. Whether you’re starting out or are a seasoned pro, you’ll find actionable insights here.
Examples use Python, but the principles apply to any language.
1. Mistake: One Test, One Assertion
The Trap:
Beginners often rigidly follow "one test, one assertion," splitting checks into tiny tests:
def test_check_title(): login_page.check_title() def test_check_email(): login_page.check_email()
The Problem:
Bloated test counts.
Harder maintenance.
Slower execution.
Cluttered reports.
The Fix:
Group logically related checks into scenario-based tests:
def test_login_form_validation(): login_page.check_title() login_page.check_email()
Key Takeaway:
Split tests by scenarios, not individual assertions.
2. Mistake: Ignoring Test IDs
The Trap:
Relying on brittle selectors like:
self.email_input = page.locator("//div//input[@class='...']")
The Problem:
Tests break with UI changes (e.g., class name updates).
The Fix:
Use dedicated test IDs (e.g., data-testid
):
self.email_input = page.get_by_test_id("login-email")
Why It Matters:
Stable tests.
Immune to UI tweaks.
3. Mistake: Allure Steps Inside Tests
The Trap:
Embedding Allure steps directly in tests:
def test_create_user(): with allure.step("Create user"): users_client.create_user()
The Problem:
Duplication. Changing a step requires updating every test.
The Fix:
Move steps to API clients/page objects:
class UsersClient: @allure.step("Create user") def create_user(self): ...
Benefits:
Cleaner tests.
Centralized step management.
4. Mistake: No API Clients
The Trap:
Calling APIs directly from tests:
response = requests.get("http://api/users/1")
The Problem:
URLs scattered everywhere.
Maintenance hell.
The Fix:
Wrap APIs in client classes:
class UsersClient: def get_user(self): return requests.get(f"{BASE_URL}/users/1")
Advantages:
Single source of truth.
Business-readable tests.
5. Mistake: Skipping PageObject
The Trap:
Writing selectors and checks inline:
def test_login(): email = page.locator("#email") assert email.is_visible()
The Fix:
Use PageObject pattern:
class LoginPage: def __init__(self, page): self.email = page.get_by_test_id("login-email") def assert_visible(self): expect(self.email).to_be_visible()
Why It’s Better:
Changes affect one file.
Tests mirror user flows.
6. Mistake: No Parametrization
The Trap:
Copy-pasting tests for different inputs:
def test_login_1(): login("user1@mail.com", "pass1") def test_login_2(): login("user2@mail.com", "pass2")
The Fix:
Use pytest parametrization:
@pytest.mark.parametrize("email, password", TEST_DATA) def test_login(email, password): ...
Benefits:
Cleaner code.
Isolated test reports.
7. Mistake: Hardcoding Values
The Trap:
Embedding configs in tests:
def test_api(): response = get("http://localhost:8000/users")
The Fix:
Externalize configurations:
# settings.py BASE_URL = os.getenv("API_URL") # test_api.py response = get(f"{settings.BASE_URL}/users")
8. Mistake: Storing Secrets in Code
The Trap:
Hardcoding credentials:
login("admin", "SuperSecret123!")
The Fix:
Use environment variables/secrets managers:
login(os.getenv("TEST_USER"), os.getenv("TEST_PWD"))
9. Mistake: Unnecessary Async
The Trap:
Using async without a real need:
@pytest.mark.asyncio async def test_api(): ...
The Fix:
Keep it synchronous unless testing async features (e.g., WebSockets).
10. Mistake: Dependent Tests
The Trap:
Tests relying on previous test states:
user_id = None def test_create_user(): global user_id user_id = create_user().id def test_delete_user(): delete_user(user_id) # Fails if test_create_user fails!
The Fix:
Isolate tests with fixtures:
@pytest.fixture def test_user(): return create_user().id def test_delete_user(test_user): delete_user(test_user)
11–15. Quick Fixes
Avoid "magic numbers": Use
HTTPStatus.BAD_REQUEST
instead of400
.Group tests: Organize into classes (e.g.,
TestLoginSuite
).Prefer
expect()
overassert
: Better UI test stability.Use data models: Pydantic/dataclasses beat raw dicts.
Leverage fixtures: Reduce boilerplate (e.g.,
scope="session"
).
Final Thoughts
Automation is engineering, not just scripting. Adopt proven patterns (PageObject, clients, fixtures) early. Refactor relentlessly.
Write tests you’d want to maintain in a year.
Labels (for Blogger.com):
AutomationTesting, SoftwareQA, PythonTesting, TestAutomation, PageObjectPattern, APITesting, Pytest, BestPractices, QualityAssurance, ContinuousTesting, Selenium, Playwright, TestDesign, CodingStandards, TechTips