Skip to content
ExponaBLOG
PLAYWRIGHT & BROWSER AUTOMATION · DAY 098 / 100

Writing readable test names: what developers should know

Tests that document behavior.

Expona Admin7 min read

Writing readable test names: what developers should know

Test names are often the first thing another engineer—or your future self—reads when debugging a failure. A poorly named test creates friction: you run the suite, see a red result, and have to dig through the test code to understand what actually broke. We've all experienced the frustration of inheriting tests named test1, test2, or shouldWork. Writing readable test names is not about style; it's about reducing cognitive load and making your Playwright suite a source of truth for your application's behavior.

Describe behavior, not implementation

The most common mistake is writing test names that describe how the code works instead of what the user experiences. When a test fails, the name should tell you what behavior broke, not which internal function was called.

Consider the difference:

typescript
// ❌ Implementation-focused
test('calls validateEmail() function', async ({ page }) => {
  await page.fill('input[type="email"]', 'invalid-email');
  await page.click('button[type="submit"]');
  // ...
});

// ✅ Behavior-focused
test('shows validation error when email format is invalid', async ({ page }) => {
  await page.fill('input[type="email"]', 'invalid-email');
  await page.click('button[type="submit"]');
  await expect(page.locator('[role="alert"]')).toContainText('Invalid email');
});

The second version tells the story: when a user enters an invalid email and submits, they see an error. If this test fails, you immediately know that email validation is broken—not that some internal function exists or doesn't exist. Implementation details change; user-facing behavior is what matters.

When naming tests, ask yourself: "If this test fails, would a product manager understand what broke?" If the answer is no, you've probably drifted into implementation territory.

Use describe blocks per feature

Playwright's describe blocks are organizational tools that mirror your feature hierarchy. They provide context before the test name is even read, which means your individual test names can be more concise while remaining clear.

Organize by user-facing feature or page section, not by technical layers:

typescript
// ✅ Feature-organized structure
describe('Checkout - Payment Form', () => {
  describe('Card validation', () => {
    test('rejects card numbers with fewer than 13 digits', async ({ page }) => {
      // ...
    });

    test('accepts valid Visa, Mastercard, and Amex numbers', async ({ page }) => {
      // ...
    });
  });

  describe('Billing address', () => {
    test('auto-fills from shipping address when checkbox is selected', async ({ page }) => {
      // ...
    });

    test('requires street address before form submission', async ({ page }) => {
      // ...
    });
  });

  describe('Form submission', () => {
    test('shows loading spinner during payment processing', async ({ page }) => {
      // ...
    });

    test('redirects to order confirmation on successful payment', async ({ page }) => {
      // ...
    });
  });
});

This structure serves multiple purposes: it tells readers exactly where in your application a test lives, it groups related tests for easier navigation in test runners, and it reduces name length because context is already provided by the hierarchy. When a test fails, the full path—"Checkout - Payment Form > Card validation > rejects card numbers with fewer than 13 digits"—paints a complete picture.

Avoid organizing by test type (unit, integration, e2e) or by file structure. Instead, organize by what users experience.

Names should explain failures

A test name should be specific enough that when it fails, you have a concrete starting point for investigation. Vague names like "works correctly" or "handles input" don't guide you toward the problem.

Compare:

typescript
// ❌ Vague
test('form works', async ({ page }) => {
  // When this fails, you have no idea what's broken
});

// ✅ Specific
test('submits form data only when all required fields are filled', async ({ page }) => {
  // If this fails, you know that either the form submitted when it shouldn't,
  // or it didn't submit when it should
});

// ✅ Even more precise if testing edge cases
test('prevents form submission when phone number exceeds 15 digits', async ({ page }) => {
  // If this fails, you know exactly which validation is broken
});

Specific names also help during code review. When a teammate sees a test named "prevents form submission when phone number exceeds 15 digits," they understand the exact scenario being tested without reading the test body. This is especially valuable in large teams or when reviewing async PRs.

Another benefit: specific names often reveal gaps in coverage. If you can't write a clear name for what you're testing, you probably don't understand what you should be testing.

Avoid numbered tests and generic placeholders

Tests named test1, test2, or shouldWork provide zero information. These are debugging artifacts—they should never make it to version control. They indicate either rushed work or tests that were copy-pasted without proper customization.

If you find yourself tempted to use generic names, step back: you probably don't have clarity on what you're testing. Use that as a signal to refine your test scope.

typescript
// ❌ Unacceptable
test('test1', async ({ page }) => { });
test('shouldWork', async ({ page }) => { });
test('this one fails sometimes', async ({ page }) => { });

// ✅ Clear and maintainable
test('displays product price in user currency based on geolocation', async ({ page }) => { });
test('recovers gracefully when API returns 500 error', async ({ page }) => { });
test('allows bulk operations on selected items in data table', async ({ page }) => { });

If a test is flaky (fails sometimes), the name should reflect the actual behavior being tested, and you should fix the test logic separately. Don't use the name as a confession of uncertainty.

Common mistakes

  • Testing implementation instead of behavior: Naming tests around internal function calls rather than user outcomes. Fix by asking "what does the user see or experience?"
  • Names that are too long: While clarity matters, a 20-word test name is usually a sign you're testing multiple things at once. Break it into separate tests or simplify the name by using describe blocks for context.
  • Inconsistent naming patterns across the suite: One section uses "should" prefixes, another uses "allows," a third uses imperative verbs. Pick a convention and stick with it for consistency. Most teams find imperative form ("displays error message") more readable than "should display error message."
  • Tests that describe setup rather than assertions: "Test with valid user and admin role" doesn't describe what you're verifying. Better: "shows edit button only to admin users."
  • Negation-heavy names: "Does not crash when network fails" is technically clear, but "recovers gracefully when network fails" is more positive and easier to understand at a glance.

Key takeaways

  • Write test names from the perspective of user behavior and outcomes, not technical implementation details.
  • Use describe blocks to organize tests by feature, allowing individual test names to be concise and focused.
  • Ensure test names are specific enough that when they fail, the name itself guides you toward what's broken.
  • Never use generic names like test1 or shouldWork; if you can't name it clearly, you haven't clarified what you're testing.
  • Adopt a consistent naming pattern across your suite to reduce cognitive friction during review and debugging.
  • A good test name serves as documentation and a debugging aid; invest in clarity upfront to reduce maintenance burden later.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles