Total Pageviews

15 Common Mistakes Beginner Automation Engineers Make (And How to Avoid Them)

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:

python
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:

python
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:

python
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):

python
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:

python
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:

python
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:

python
response = requests.get("http://api/users/1")  

The Problem:

  • URLs scattered everywhere.

  • Maintenance hell.

The Fix:
Wrap APIs in client classes:

python
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:

python
def test_login():  
    email = page.locator("#email")  
    assert email.is_visible()  

The Fix:
Use PageObject pattern:

python
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:

python
def test_login_1():  
    login("user1@mail.com", "pass1")  

def test_login_2():  
    login("user2@mail.com", "pass2")  

The Fix:
Use pytest parametrization:

python
@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:

python
def test_api():  
    response = get("http://localhost:8000/users")  

The Fix:
Externalize configurations:

python
# 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:

python
login("admin", "SuperSecret123!")  

The Fix:
Use environment variables/secrets managers:

python
login(os.getenv("TEST_USER"), os.getenv("TEST_PWD"))  

9. Mistake: Unnecessary Async

The Trap:
Using async without a real need:

python
@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:

python
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:

python
@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 of 400.

  • Group tests: Organize into classes (e.g., TestLoginSuite).

  • Prefer expect() over assert: 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