Edge Case Testing in Playwright
Testing the unhappy paths: empty input, unusual data, boundaries, and invalid interactions.
Edge case testing verifies how an application behaves under unusual, unexpected, invalid, or extreme conditions beyond the normal happy path.
The goal is to ensure the application handles errors gracefully instead of crashing, producing incorrect results, or exposing security vulnerabilities.
1. Common Edge Case Scenarios
Empty Inputs
Test submitting forms without providing required data.
test('should reject an empty login form', async ({ page }) => {
await page.goto('/login');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByLabel('Email')).toBeVisible();
await expect(page.getByText(/required/i)).toBeVisible();
});
Non-standard or Unexpected Data
Test how the application handles unusual characters, symbols, and unexpected input.
test('should handle unusual search input', async ({ page }) => {
await page.goto('/search');
await page.getByRole('textbox', { name: 'Search' })
.fill('!@#$%^&*()');
await page.keyboard.press('Enter');
await expect(page.getByText(/error occurred/i))
.toHaveCount(0);
});
Whitespace Handling
Check whether inputs containing only spaces are rejected or normalized appropriately.
test('should reject whitespace-only input', async ({ page }) => {
await page.goto('/signup');
await page.getByLabel('Name').fill(' ');
await page.getByRole('button', { name: 'Sign Up' }).click();
await expect(page.getByText(/name is required/i))
.toBeVisible();
});
Security Vulnerabilities (XSS)
Test whether potentially malicious input is handled safely.
test('should safely handle script-like input', async ({ page }) => {
await page.goto('/search');
await page.getByRole('textbox', { name: 'Search' })
.fill('<script>alert(1)</script>');
await page.keyboard.press('Enter');
// The input should not execute as JavaScript.
await expect(page.getByRole('alert')).toHaveCount(0);
});
For stronger XSS verification, explicitly monitor JavaScript execution or unexpected dialogs and verify that the input is rendered as text rather than executable HTML. The absence of an alert alone does not prove the application is secure.
Invalid Interactions
Attempt actions that should be blocked by application rules.
test('should prevent checkout with an empty cart', async ({ page }) => {
await page.goto('/cart');
await expect(
page.getByRole('button', { name: 'Checkout' })
).toBeDisabled();
});
2. Other Important Edge Cases
- Boundary values: Minimum, maximum, and just-outside-limit values.
- Long inputs: Extremely long strings or text exceeding field limits.
- Invalid formats: Incorrect email addresses, malformed dates, or invalid phone numbers.
- Repeated actions: Double-clicking submit buttons or submitting a form repeatedly.
- Network failures: API timeouts, server errors, and offline conditions.
- Unauthorized access: Attempting to access protected pages without authentication.
- Missing data: Handling empty API responses, missing fields, or unexpected response structures.
- Concurrency: Multiple requests modifying the same resource simultaneously.
3. Example: Boundary Value Testing
Suppose a username must contain between 3 and 20 characters.
test.describe('Username boundary validation', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/signup');
});
test('should reject a username shorter than 3 characters', async ({ page }) => {
await page.getByLabel('Username').fill('ab');
await expect(page.getByText(/at least 3 characters/i))
.toBeVisible();
});
test('should accept a username with 3 characters', async ({ page }) => {
await page.getByLabel('Username').fill('abc');
await expect(page.getByText(/at least 3 characters/i))
.toHaveCount(0);
});
test('should reject a username longer than 20 characters', async ({ page }) => {
await page.getByLabel('Username').fill('a'.repeat(21));
await expect(page.getByText(/maximum 20 characters/i))
.toBeVisible();
});
});
The exact assertions depend on the application's validation behavior and UI.
4. Key Principles
- Think like an unexpected user: Consider accidental mistakes and deliberate misuse.
- Test boundaries: Focus on minimums, maximums, empty values, and invalid formats.
- Verify graceful failure: Ensure errors are handled without crashes or corrupted state.
- Check security: Ensure user input cannot execute unintended code or bypass authorization.
- Test one condition at a time: Keep tests focused and failures easy to diagnose.
- Verify outcomes: Assert the expected error, blocked action, or safe behavior rather than merely performing an action.
5. Running Edge Case Tests
# Run all tests
npx playwright test
# Run a specific edge case test file
npx playwright test tests/edge-cases.spec.ts
# Run tests matching a title
npx playwright test -g "boundary"
# Run with a visible browser
npx playwright test tests/edge-cases.spec.ts --headed
# Debug a failing test
npx playwright test tests/edge-cases.spec.ts --debug
Golden rule: For every situation where something could go wrong, identify the expected safe behavior and write a test that verifies it.