The login page itself deserves focused, thorough test coverage since it's the entry point to nearly everything else. This lesson covers testing valid and invalid login scenarios, validation, and account lockout behavior.
What a Complete Login Test Suite Covers
Beyond the "happy path" of a successful login, a thorough login test suite covers invalid credentials, empty/malformed input validation, and any rate-limiting or account lockout behavior the application implements after repeated failures.
Test both the success path and each meaningful failure path separately.
Confirm error messages don't leak whether a specific email exists (a security best practice).
Verify client-side validation prevents submission of obviously invalid input.
If lockout/rate-limiting exists, test that it actually triggers after the configured threshold.
Login Test Scenarios Cheat Sheet
A checklist of login scenarios worth covering in most applications.
Scenario
Expected Behavior
Valid email + password
Redirects to authenticated area
Valid email + wrong password
Shows a generic invalid-credentials error
Unregistered email
Shows the same generic error (no account-existence leak)
Empty email/password
Client-side validation error, no request sent
Malformed email format
Client-side validation error
Too many failed attempts
Rate limiting or temporary lockout message
Already logged in, visits /login
Redirects away from login automatically
Testing Invalid Credentials Correctly
A well-designed login error message reveals as little as possible about why a login failed, showing the same generic message whether the email doesn't exist or the password is simply wrong. Testing this consistency directly guards against an accidental security regression.
it('shows a generic error for invalid credentials, regardless of cause', () => {
cy.visit('/login');
cy.get('[data-cy="email"]').type('nonexistent@example.com');
cy.get('[data-cy="password"]').type('anything');
cy.get('[data-cy="submit"]').click();
cy.get('[data-cy="login-error"]').should('contain', 'Invalid email or password');
});
Testing Rate Limiting or Account Lockout
If your application locks an account (or throttles attempts) after repeated failures, stubbing the backend response for that specific scenario is often more reliable than actually triggering real lockout logic against a live backend during a test run.
cy.intercept('POST', '/api/login', {
statusCode: 429,
body: { message: 'Too many attempts. Try again in 5 minutes.' },
}).as('loginRateLimited');
cy.get('[data-cy="submit"]').click();
cy.wait('@loginRateLimited');
cy.get('[data-cy="login-error"]').should('contain', 'Too many attempts');
Common Mistakes
Only testing the successful login path and skipping invalid-credential and validation scenarios entirely.
Revealing whether an email exists through different error messages, and never having a test that would catch this regression.
Attempting to trigger real rate-limiting/lockout logic repeatedly against a live backend instead of stubbing it.
Not testing that an already-authenticated user is redirected away from the login page automatically.
Key Takeaways
A complete login test suite covers success, invalid credentials, validation, and lockout scenarios.
Generic error messages for invalid credentials should be tested explicitly as a security safeguard.
Stubbing rate-limiting responses is more reliable than triggering real lockout logic during tests.
Redirect behavior for already-authenticated users visiting the login page is worth its own test.
Pro Tip
Add a dedicated test asserting that the login error message is character-for-character identical for both "wrong password" and "unknown email" scenarios, this is a security property that is easy to accidentally break during an unrelated refactor, and a direct test is the cheapest way to guard it.
You now have thorough login test coverage patterns. Next, learn how cy.session() speeds up and simplifies authenticated tests.