Test Frameworks & Tools

TL;DR

Use Playwright for modern browser testing (fastest, best DX). Use pytest for Python backend tests. Use Selenium when you need multi-language support or have legacy suites. Use Cypress for JavaScript-heavy teams. Use JUnit for Java ecosystems.

Explain Like I'm 12

A test framework is like a recipe book for checking things. Instead of figuring out how to open a browser, click buttons, and read text from scratch — the framework gives you ready-made instructions like "go to this page" and "click this button."

Different frameworks are like different recipe books — some are for Italian cooking (web apps), some for baking (APIs), some for both. You pick the one that matches what you're cooking.

The Framework Landscape

Test frameworks fall into two broad categories: unit/backend frameworks (test code logic) and UI/browser frameworks (test what users see). Most real projects use one from each category.

Test framework landscape showing unit frameworks (pytest, JUnit, Jest) and browser frameworks (Playwright, Cypress, Selenium) with their ecosystems

Framework Comparison

FrameworkLanguageTest TypeSpeedBest For
PlaywrightPython, JS, Java, .NETUI / E2EFastModern cross-browser testing
CypressJavaScript onlyUI / E2EFastJS-heavy frontend teams
SeleniumPython, Java, JS, C#, RubyUI / E2EMediumLegacy suites, multi-language teams
pytestPythonUnit / IntegrationVery fastPython backend/API testing
JUnit 5Java / KotlinUnit / IntegrationVery fastJava ecosystem, Spring Boot
JestJavaScript / TypeScriptUnit / IntegrationVery fastReact/Node.js projects
Tip: Don't pick a framework based on popularity alone. Consider your team's language, what you're testing (UI vs API vs unit), and how it fits into your CI pipeline. The "best" framework is the one your team will actually use consistently.

Playwright

Playwright (by Microsoft) is the modern standard for browser testing. It's fast, reliable, and supports Chrome, Firefox, and Safari from a single API. Built-in auto-wait eliminates most flakiness issues.

# Install: pip install playwright && playwright install
from playwright.sync_api import sync_playwright

def test_search_feature():
    with sync_playwright() as p:
        browser = p.chromium.launch()
        page = browser.new_page()
        page.goto("https://example.com")
        page.fill("#search", "QA automation")
        page.click("button.search-btn")
        assert page.locator(".results").count() > 0
        browser.close()

Key strengths:

  • Auto-wait — Automatically waits for elements to be ready before interacting
  • Multi-browser — Chrome, Firefox, WebKit (Safari) from one API
  • Codegen — Record browser actions and generate test code automatically
  • Trace viewer — Visual debugger with screenshots, network logs, and DOM snapshots
  • API testing — Built-in support for HTTP requests alongside browser tests
Info: Playwright's codegen tool is great for getting started: run playwright codegen https://yourapp.com to record interactions and generate test code. Then refactor the output into Page Objects.

Cypress

Cypress is a JavaScript-first testing framework that runs inside the browser. It's popular with frontend teams because tests look like the app code they already write.

// Install: npm install cypress --save-dev
describe('Login Flow', () => {
  it('should login with valid credentials', () => {
    cy.visit('/login')
    cy.get('#email').type('[email protected]')
    cy.get('#password').type('secret123')
    cy.get('button[type="submit"]').click()
    cy.url().should('include', '/dashboard')
    cy.get('.welcome').should('contain', 'Welcome, Alice')
  })
})

Key strengths:

  • Time travel — Snapshots at each step; hover to see what the app looked like
  • Auto-reload — Tests re-run automatically when you save files
  • Network stubbing — Intercept and mock API responses easily
Warning: Cypress has limitations: it only supports JavaScript/TypeScript, can't test multiple browser tabs, and historically only supported Chrome (Firefox and WebKit support is experimental). If you need multi-language or Safari testing, consider Playwright.

Selenium

Selenium is the original browser automation framework (since 2004). It supports the most languages and has the largest ecosystem, but requires more setup and explicit waits than modern alternatives.

# Install: pip install selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

driver = webdriver.Chrome()
driver.get("https://example.com/login")

# Explicit wait — Selenium doesn't auto-wait like Playwright
email = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "email"))
)
email.send_keys("[email protected]")
driver.find_element(By.ID, "password").send_keys("secret123")
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()

assert "/dashboard" in driver.current_url
driver.quit()
Info: Selenium 4 introduced Selenium Manager (auto-downloads browser drivers) and relative locators ("find the button near the email field"). If your Selenium code still manually manages ChromeDriver, it's time to upgrade.

pytest

pytest is Python's de facto testing framework. It's not just for unit tests — with plugins, it handles everything from API testing to database integration tests to Playwright browser tests.

# Install: pip install pytest
# Run: pytest tests/ -v

# Simple test — no classes needed
def test_addition():
    assert 1 + 1 == 2

# Fixtures for setup/teardown
import pytest

@pytest.fixture
def api_client():
    client = TestClient(app)
    yield client

def test_get_users(api_client):
    response = api_client.get("/api/users")
    assert response.status_code == 200
    assert len(response.json()) > 0

# Parametrized tests — run same test with different inputs
@pytest.mark.parametrize("input,expected", [
    (2, 4), (3, 9), (4, 16), (-1, 1)
])
def test_square(input, expected):
    assert input ** 2 == expected

Essential plugins:

PluginPurpose
pytest-covCode coverage reporting
pytest-xdistParallel test execution
pytest-playwrightPlaywright browser test integration
pytest-htmlGenerate HTML test reports
pytest-mockEasy mocking with unittest.mock
Tip: Use pytest -x to stop on first failure during development (fast feedback). Use pytest -n auto (with pytest-xdist) in CI for parallel execution.

JUnit 5

JUnit 5 is the standard testing framework for Java and Kotlin. It integrates tightly with Maven, Gradle, and Spring Boot.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;

class CalculatorTest {

    @Test
    @DisplayName("Addition should return correct sum")
    void testAddition() {
        Calculator calc = new Calculator();
        assertEquals(4, calc.add(2, 2));
    }

    @ParameterizedTest
    @ValueSource(ints = {1, 2, 3, 5, 8})
    void testIsPositive(int number) {
        assertTrue(number > 0);
    }
}
Info: For Java API testing, pair JUnit with REST Assured for fluent HTTP assertions: given().when().get("/users").then().statusCode(200). For Spring Boot, use @SpringBootTest + @AutoConfigureMockMvc.

How to Choose

Your SituationRecommended Stack
Python backend + any frontendpytest (unit/integration) + Playwright (E2E)
JavaScript/TypeScript full-stackJest (unit) + Playwright or Cypress (E2E)
Java / Spring BootJUnit 5 (unit) + Selenium or Playwright (E2E)
Multi-language team, legacy codebaseSelenium (common API across languages)
Mobile app testingAppium (extends Selenium for iOS/Android)
API-only (no UI)pytest + requests or REST Assured
Tip: Start with one framework and get good at it. You can always add another later. Teams that try to adopt three frameworks simultaneously usually master none.

Test Yourself

What's the key advantage of Playwright over Selenium for browser testing?

Playwright has built-in auto-wait — it automatically waits for elements to be ready before interacting. Selenium requires explicit waits (WebDriverWait), which leads to more boilerplate code and more flaky tests when waits are misconfigured. Playwright also supports all browsers from a single API and includes built-in features like trace viewer and codegen.

When would you choose Selenium over Playwright?

Choose Selenium when: (1) you have a large existing Selenium test suite and migration cost is too high, (2) you need language support that Playwright doesn't offer (Ruby, PHP), (3) you need Appium for mobile testing (which extends the Selenium protocol), or (4) your team already has deep Selenium expertise.

Why might a JavaScript team choose Cypress over Playwright?

Cypress runs inside the browser, which means it has direct access to the application's JavaScript context — you can stub functions, mock network requests, and inspect the DOM natively. It also has excellent developer experience with time-travel debugging and automatic test reruns on file save. However, it's limited to JavaScript and has weaker multi-browser support than Playwright.

What makes pytest fixtures better than setUp/tearDown methods?

pytest fixtures are composable and explicit. Instead of a monolithic setUp that runs before every test, each test declares exactly which fixtures it needs as function parameters. Fixtures can depend on other fixtures, have different scopes (function, class, module, session), and use yield for clean teardown. This makes tests more readable and avoids unnecessary setup.

What is parametrized testing and why is it useful?

Parametrized testing runs the same test logic with different inputs. Instead of writing 5 separate tests for 5 different inputs, you write one test and supply a list of (input, expected) pairs. It reduces code duplication and makes it easy to add new test cases. Both pytest (@pytest.mark.parametrize) and JUnit (@ParameterizedTest) support this natively.

Interview Questions

How would you decide between Playwright and Selenium for a new project?

For a new project, I'd default to Playwright because of auto-wait (fewer flaky tests), built-in multi-browser support, better debugging tools (trace viewer, codegen), and faster execution. I'd choose Selenium only if the team needs Ruby/PHP language support, needs Appium for mobile testing, or has significant existing Selenium infrastructure they can't migrate from.

Explain the Page Object Model and why it matters for large test suites.

POM encapsulates each page's UI elements and interactions in a dedicated class. In large suites with hundreds of tests, UI changes (like renaming a CSS class) would require updating every test file that references that element. With POM, you update one class and all tests automatically use the updated selectors. This makes the suite maintainable as both the app and test count grow.

How do you handle test flakiness in browser-based tests?

Key strategies: (1) Use auto-wait frameworks (Playwright) instead of sleep/explicit waits. (2) Isolate test data — don't share state between tests. (3) Retry flaky tests with a limit (e.g., pytest-rerunfailures) to distinguish flaky from truly broken. (4) Track flaky test rate as a metric and quarantine persistent offenders. (5) Use deterministic selectors (data-testid) instead of fragile CSS/XPath.