Testing Frameworks: Vitest, Jest, Playwright, Cypress, pytest
Introduction
Testing frameworks have evolved significantly. Vitest has become the dominant choice for JavaScript unit testing, Playwright leads browser testing, and pytest remains the Python standard. This comparison covers the frameworks you need in your testing toolbox, with setup examples and best practices.
Vitest
Vitest is the modern JavaScript test runner, native ESM, and Vite-integrated:
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./test/setup.ts"],
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
exclude: ["src/types/**"],
},
include: ["src/**/*.{test,spec}.{ts,tsx}"],
mockReset: true,
testTimeout: 10000,
},
});
// counter.test.ts
import { describe, it, expect, vi, beforeEach } from "vitest";
import { increment, decrement } from "./counter";
// Mock a module
vi.mock("./api", () => ({
fetchCount: vi.fn().mockResolvedValue(42),
}));
describe("counter", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("increments correctly", () => {
expect(increment(5)).toBe(6);
});
it("decrements correctly", () => {
expect(decrement(5)).toBe(4);
});
it("handles edge cases", () => {
expect(increment(Infinity)).toBe(Infinity);
});
});
**Strengths**: Near-instant watch mode (HMR for tests), Jest-compatible API, native TypeScript, ESM support, excellent performance, built-in coverage and mocking.
Jest
The established standard, still widely used in older projects:
// jest.config.js
module.exports = {
testEnvironment: "jsdom",
transform: {
"^.+\\.tsx?$": "ts-jest",
},
moduleNameMapper: {
"\\.(css|less)$": "
},
setupFilesAfterSetup: ["./jest.setup.js"],
collectCoverageFrom: ["src/**/*.{ts,tsx}"],
};
**Strengths**: Mature ecosystem, extensive documentation, stable API.
**Weaknesses**: Slower than Vitest, CJS-focused, heavier configuration, ts-jest is slower than Vitest's esbuild transpilation.
Playwright
The leading browser testing framework by Microsoft:
// playwright.config.ts
import { defineConfig } from "@playwright/test";
export default defineConfig({
testDir: "./e2e",
fullyParallel: true,
retries: 2,
workers: 4,
reporter: [["html"], ["list"]],
use: {
baseURL: "http://localhost:3000",
trace: "on-first-retry",
screenshot: "only-on-failure",
video: "retain-on-failure",
},
projects: [
{ name: "chromium", use: { browserName: "chromium" } },
{ name: "firefox", use: { browserName: "firefox" } },
{ name: "webkit", use: { browserName: "webkit" } },
],
});
// login.spec.ts
import { test, expect } from "@playwright/test";
test("user can log in", async ({ page }) => {
await page.goto("/login");
await page.fill("#email", "user@example.com");
await page.fill("#password", "password123");
await page.click("button[type='submit']");
await expect(page.locator(".welcome")).toHaveText("Welcome back!");
await expect(page).toHaveURL("/dashboard");
});
**Strengths**: Cross-browser, fast execution, auto-waiting, network interception, debugging tools, component testing.
Cypress
An alternative browser testing framework with a unique architecture:
// cypress.config.js
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
baseUrl: "http://localhost:3000",
specPattern: "cypress/e2e/**/*.cy.js",
video: false,
screenshotOnRunFailure: true,
},
component: {
devServer: {
framework: "react",
bundler: "vite",
},
},
});
**Strengths**: Excellent debugging with time-travel, real-time reloading, great documentation, network stubbing.
**Weaknesses**: Slower than Playwright, limited to Chromium-based browsers, less ideal for parallel execution.
pytest
The standard Python testing framework:
# conftest.py
import pytest
import pytest_asyncio
@pytest.fixture
def database():
db = create_test_database()
yield db
cleanup_database(db)
@pytest_asyncio.fixture
async def async_client():
client = await create_async_client()
yield client
await client.close()
# test_api.py
import pytest
from httpx import AsyncClient
class TestUserAPI:
@pytest.mark.asyncio
async def test_create_user(self, async_client):
response = await async_client.post("/users", json={
"name": "Alice",
"email": "alice@example.com",
})
assert response.status_code == 201
assert response.json()["name"] == "Alice"
@pytest.mark.parametrize("email,expected", [
("invalid", 422),
("", 422),
("valid@example.com", 201),
])
async def test_email_validation(self, async_client, email, expected):
response = await async_client.post("/users", json={"email": email})
assert response.status_code == expected
Comparison
| Feature | Vitest | Jest | Playwright | Cypress | pytest |
|---------|--------|------|-----------|---------|--------|
| Type | Unit/Integration | Unit/Integration | Browser E2E | Browser E2E | Unit/Integration |
| Speed | Fast | Moderate | Fast | Moderate | Fast |
| Language | TS/JS | TS/JS | TS/JS | JS | Python |
| Configuration | Simple | Moderate | Simple | Simple | Simple |
| Watch mode | Instant | Good | N/A | N/A | pytest-watch |
Recommendations
* **JavaScript unit tests**: Vitest is the default choice for new projects.
* **Existing Jest projects**: Not urgent to migrate, but consider Vitest for new test files.
* **Browser testing**: Playwright for cross-browser E2E tests.
* **Component testing**: Playwright or Vitest (with JSDOM).
* **Python testing**: pytest with pytest-asyncio for async code.
The ideal testing stack in 2026: Vitest for unit tests, Playwright for E2E tests, pytest for Python services.