How to Write Test Cases for Login Flow (With Examples)

How to Write Test Cases for Login Flow (With Examples) is a practical guide that shows you how to design, organize, and execute test cases that give real confidence in one of the most critical user pa

June 20, 2026 · 17 min read · How-To Guides

How to Write Test Cases for Login Flow (With Examples) is a practical guide that shows you how to design, organize, and execute test cases that give real confidence in one of the most critical user pathways in any application. Login is often the first interaction a user has with a system, and defects here can block access, damage trust, or expose security gaps. This article walks you through the full lifecycle of test‑case creation: from gathering requirements to structuring each case, from building a comprehensive matrix to linking cases to requirements, prioritizing effort, and blending manual design with automated execution and autonomous exploration. You will find concrete examples, a ready‑to‑use test table with over twenty cases, data‑setup strategies, checklists, and code snippets that you can adapt to Android, iOS, or web stacks. By the end, you will have a repeatable process you can bookmark and apply to any login flow, whether you are writing tests by hand or letting a tool like SUSATest generate regression scripts from its autonomous runs.

1. Understanding Login Flow Requirements

Before you write a single test case, you need a clear picture of what the login flow is supposed to do. Requirements may live in user stories, acceptance criteria, API contracts, or design sketches. Treat them as the source of truth for every test you will later create.

1.1 Extracting User Stories and Acceptance Criteria

Start by locating the story that describes login. A user can authenticate with email/password. Typical acceptance criteria might read:

Write each criterion in Given/When/Then form; this makes it trivial to map to test cases later.

1.2 Identifying Non‑Functional Aspects

Login also carries performance, security, and usability expectations. Note down:

Capture these as separate requirement items; they will generate additional test categories (boundary, security, accessibility).

1.3 Creating a Requirements Traceability Matrix (RTM)

A lightweight RTM can be a simple spreadsheet with three columns: Requirement ID, Description, Linked Test Case IDs. As you write cases, fill in the Test Case IDs column. This gives you instant visibility: if a requirement has zero linked cases, you know you missed something. If a requirement has many cases, you can review for redundancy.

2. Anatomy of a Test Case

A well‑structured test case is readable by both humans and test‑management tools. Consistency reduces ambiguity and makes maintenance easier.

2.1 Core Fields

FieldPurpose
Test IDUnique identifier (e.g., LOGIN‑001). Enables traceability and referencing in defect reports.
TitleShort, descriptive summary (e.g., “Valid credentials redirect to dashboard”).
PreconditionsState that must be true before execution (e.g., “User exists in test database with password ‘Abcdef1!’”).
Test StepsOrdered actions the tester performs. Each step should be atomic and observable.
Expected ResultWhat the system should do after the last step (e.g., “Dashboard page loads, URL contains /dashboard, auth token present in storage”).
PostconditionsOptional cleanup (e.g., “Log out user to reset state”).
DataInput values used (e.g., email, password). Can be inline or referenced from a data set.
AttachmentsScreenshots, mockups, or API contracts that help clarify the step.

2.2 Writing Effective Steps

Use imperative mood and avoid conjunctions that hide multiple actions. Bad: “Enter email and password then click Sign In and verify you are logged in.” Good:

  1. Open the login screen.
  2. In the Email field, enter user@example.com.
  3. In the Password field, enter Abcdef1!.
  4. Tap the Sign In button.
  5. Verify that the dashboard screen appears within 2 seconds.
  6. Verify that the auth token is stored in secure storage.

Each step maps to a single UI interaction or verification point, which simplifies automation.

2.3 Leveraging Templates

Most test‑management tools (TestRail, Zephyr, Xray) allow you to define a template. Create a login‑specific template that already contains the Preconditions and Postconditions sections filled with common placeholders (e.g., “Ensure the app is installed and cleared of previous session data”). Then you only need to vary Title, Steps, Expected Result, and Data.

2.4 Note on Auto‑Generated Scripts

When you later run an autonomous explorer such as SUSATest, it can observe the steps you defined and output ready‑to‑run Appium (Android) or Playwright (Web) scripts. This bridges the gap between manual case design and automated regression without rewriting logic.

3. Categorizing Test Cases: Positive, Negative, Edge, Boundary

Grouping cases by intent helps you prioritize and ensures you cover complementary aspects of the flow.

3.1 Positive Cases

Positive cases verify that the happy path works under normal conditions. They confirm that valid inputs lead to the expected successful outcome. Examples:

3.2 Negative Cases

Negative cases validate that the system correctly rejects invalid or malicious input. They should also check that error messages are helpful and that the system does not leak information (e.g., distinguishing “unknown email” vs “wrong password”).

3.3 Edge Cases

Edge cases sit at the borders of valid input domains or involve unusual interaction patterns. They often uncover bugs that only appear under stress or with specific data sets.

3.4 Boundary Cases

Boundary cases test the limits defined by requirements (length, count, rate). They are a subset of edge cases but deserve explicit mention because they are easy to automate with data‑driven loops.

4. Building a Test Matrix for Login Flow

Below is a concrete matrix of 24 test cases that covers positive, negative, edge, and boundary scenarios. Feel free to copy it into your test‑management tool and adapt the IDs, data, or expected results to your specific implementation.

TC IDTitlePreconditionsStepsExpected ResultTypePriority
LOGIN-001Valid email/password redirects to dashboardUser alice@example.com exists with password Secure12!1. Launch app 2. Enter email alice@example.com 3. Enter password Secure12! 4. Tap Sign InDashboard loads, URL /dashboard, auth token stored, welcome message shows Alice’s namePositiveP0
LOGIN-002Valid social login (Google)Test Google account testuser@gmail.com with password known1. Launch app 2. Tap Sign In with Google 3. Enter Google credentials 4. ConsentDashboard loads, token present, user profile shows Google emailPositiveP0
LOGIN-003Remember‑me keeps session after restartUser bob@example.com exists, remember‑me enabled1. Log in with valid creds, check Remember me 2. Close app 3. Reopen appApp opens directly to dashboard without prompting for credentialsPositiveP1
LOGIN-004Invalid email format shows errorNo preconditions needed1. Launch app 2. Enter email notanemail 3. Enter any password 4. Tap Sign InInline error: “Please enter a valid email address”NegativeP0
LOGIN-005Non‑existent email returns generic errorEnsure no user with email unknown@domain.com1. Launch app 2. Enter email unknown@domain.com 3. Enter password AnyPass1! 4. Tap Sign InToast: “Invalid email or password” (does not reveal whether email exists)NegativeP0
LOGIN-006Correct email, wrong passwordUser carol@example.com exists with password RealPass1!1. Launch app 2. Enter email carol@example.com 3. Enter password WrongPass 4. Tap Sign InError: “Invalid email or password”NegativeP0
LOGIN-007Password missing required special charPolicy: at least one special character1. Launch app 2. Enter email dave@example.com 3. Enter password NoSpecial123 (only letters/numbers) 4. Tap Sign InError: “Password must contain at least one special character”NegativeP1
LOGIN-008Password too short (below min)Policy: min length 81. Launch app 2. Enter email eve@example.com 3. Enter password short (5 chars) 4. Tap Sign InError: “Password must be at least 8 characters” characters long”NegativeP1
LOGIN-009Password at max length (boundary)Policy: max length 641. Launch app 2. Enter email frank@example.com 3. Enter password a repeated 64 times 4. Tap Sign InLogin succeeds, token issuedBoundaryP2
LOGIN-010Password exceeds max length (boundary)Policy: max length 641. Launch app 2. Enter email grace@example.com 3. Enter password a repeated 65 times 4. Tap Sign InError: “Password must be no longer than 64 characters”BoundaryP2
LOGIN-011Email with leading/trailing spacesNo preconditions1. Launch app 2. Enter email space@example.com (note spaces) 3. Enter valid password 4. Tap Sign InSystem trims spaces and logs in successfully OR shows error if trimming not implemented (specify expected)EdgeP2
LOGIN-012Email with consecutive dotsRFC allows but some systems reject1. Launch app 2. Enter email user..name@example.com 3. Enter valid password 4. Tap Sign InEither success (if allowed) or error “Invalid email format” (based on spec)EdgeP2
LOGIN-013Password containing emojiUnicode support1. Launch app 2. Enter email henry@example.com 3. Enter password Pass😀word! 4. Tap Sign InLogin succeeds (if Unicode allowed) or error if rejectedEdgeP2
LOGIN-014SQL injection attempt in email fieldNo preconditions1. Launch app 2. Enter email ' OR 1=1-- 3. Enter any password 4. Tap Sign InError: “Invalid email address” (no SQL execution)Negative (Security)P0
LOGIN-015XSS attempt in password fieldNo preconditions1. Launch app 2. Enter email victim@example.com 3. Enter password 4. Tap Sign InError or sanitized input; no script execution in subsequent pagesNegative (Security)P0
LOGIN-016Empty fields submissionNo preconditions1. Launch app 2. Leave email blank 3. Leave password blank 4. Tap Sign InInline errors for both fields: “Email is required”, “Password is required”NegativeP0
LOGIN-017Whitespace only emailNo preconditions1. Launch app 2. Enter email (spaces) 3. Enter valid password 4. Tap Sign InError: “Email is required” or “Invalid email address”EdgeP1
LOGIN-018Whitespace only passwordNo preconditions1. Launch app 2. Enter valid email 3. Enter password (spaces) 4. Tap Sign InError: “Password is required” or “Password must contain at least …”EdgeP1
LOGIN-019Rapid double‑tap on Sign InNo preconditions1. Launch app 2. Enter valid creds 3. Tap Sign In twice within 200msOnly one authentication request sent; second tap ignored or shows “Already processing”EdgeP2
LOGIN-020Network switch mid‑authenticationDevice connected to Wi‑Fi1. Launch app 2. Enter valid creds 3. Tap Sign In 4. Immediately disable Wi‑Fi and enable cellularAuthentication either completes successfully with retry or shows clear network error; no crashEdgeP2
LOGIN-021Account lockout after 5 failed attemptsLockout threshold = 51. Launch app 2. Enter valid email lock@example.com 3. Enter wrong password 4. Tap Sign In (repeat 4 more times) 5. On 6th attempt, enter correct passwordAfter 5th failed attempt: message “Account temporarily locked”. 6th attempt (even with correct creds) shows same lockout message.NegativeP0
LOGIN-022CAPTCHA appears after rate‑limit thresholdRate‑limit = 10 failures/min per IP1. Using a script or manual rapid attempts, fail login 10 times quickly 2. On 11th attempt, enter correct credentialsCAPTCHA widget appears; login blocked until solvedNegativeP1
LOGIN-023Accessibility: label associationScreen reader (TalkBack/VoiceOver) enabled1. Launch app 2. Focus on Email field 3. Activate screen readerReader announces “Email, edit text, required”. Same for Password and Sign In buttonAccessibilityP1
LOGIN-024Performance: login under 2 secondsNetwork latency simulated at 150ms RTT1. Launch app 2. Enter valid creds 3. Tap Sign In 4. Measure time to dashboard loadTime to dashboard ≤ 2000msPerformanceP1

How to read the table

You can import this CSV into most test‑case managers and then add automation IDs or tags.

5. Data Setup and Test Data Management

Reliable login tests depend on predictable data. Flaky data leads to false positives and erodes confidence.

5.1 Static vs Dynamic Data

Choose static for smoke suites and dynamic for regression or load‑testing suites.

5.2 Example: JSON Seed File


[
  {
    "email": "alice@example.com",
    "password": "Secure12!",
    "firstName": "Alice",
    "lastName": "Anderson",
    "enabled": true
  },
  {
    "email": "bob@example.com",
    "password": "Another!456",
    "firstName": "Bob",
    "lastName": "Baker",
    "enabled": false
  }
]

Load this file in your test setup script (see code snippets in Section 7) and insert the records into the test database or call a test‑only endpoint that creates them.

5.3 Using Factory Libraries

In Java with DataFaker or Java‑Faker:


public User randomUser() {
    return new User()
            .setEmail(faker.internet().emailAddress())
            .setPassword(faker.internet().password(10, 20, true, true, true))
            .setEnabled(true);
}

In Python with factory_boy:


class UserFactory(factory.Factory):
    class Meta:
        model = User

    email = factory.Faker('email')
    password = factory.LazyAttribute(lambda _: f"{factory.Faker('password').generate(length=12)}Aa!")
    is_active = True

Call the factory in a beforeEach hook to ensure each test starts with a clean slate.

5.4 Mocking External Identity Providers

If your login delegates to OAuth providers, you can avoid flaky third‑party calls by mocking the token endpoint. Tools like WireMock (Java) or msw (Mock Service Worker, JavaScript) let you define:

Record the mock mappings in source control so they travel with your test code.

5.5 Cleanup Strategies

After each test, you should:

Automate cleanup in an afterEach hook; otherwise, tests may start interfering with each other.

6. Prioritization and Risk‑Based Testing

Not all test cases carry equal weight. Use a risk matrix to decide what to run first, especially when time is limited.

6.1 Building a Simple Risk Matrix

Impact \ LikelihoodLow (1)Medium (2)High (3)
Low (1)123
Medium (2)246
High (3)369

Score each test case by assigning Impact (1‑3) based on business consequence (e.g., lockout = high, UI typo = low) and Likelihood (1‑3) based on historical defect density or complexity. Multiply to get a Risk Priority Number (RPN). Higher RPN → higher execution priority.

6.2 Example RPN Assignment

TC IDImpactLikelihoodRPNPriority (derived)
LOGIN-001339P0
LOGIN-004236P0
LOGIN-005236P0
LOGIN-006236P0
LOGIN-007224P1
LOGIN-008224P1
LOGIN-009122P2
LOGIN-010122P2
LOGIN-011111P3
LOGIN-012111P3
LOGIN-013111P3
LOGIN-014326P0 (security)
LOGIN-015326P0 (security)
LOGIN-016236P0
LOGIN-017122P2
LOGIN-018122P2
LOGIN-019122P2
LOGIN-020224P1
LOGIN-021339P0
LOGIN-022224P1
LOGIN-023224P1 (accessibility)
LOGIN-024236P1 (performance)

Map RPN ranges to your P0‑P3 buckets (e.g., 7‑9 → P0, 4‑6 → P1, 2‑3 → P2, 1 → P3). This gives you a defensible ordering that stakeholders can review.

6.3 Traceability to Requirements

Link each TC ID to the requirement IDs from your RTM. For instance:

When a requirement changes, you can instantly see which test cases need review by filtering the RTM.

7. Manual vs Automated Approaches

Designing test cases manually is the first step; turning them into automated checks provides regression safety and enables frequent execution.

7.1 Writing Manual Test Cases

Use the template from Section 2.2. Keep the language imperative and avoid vague phrases like “verify that the user is logged in”. Instead, specify the observable outcome: “Check that the dashboard header contains the user’s first name” or “Confirm that an auth token is present in SecureStorage”.

When you write the case, also note any test data needed (email, password) and any setup (e.g., “Ensure the user is not already logged in”). This makes hand‑off to automation straightforward.

7.2 Converting to Automated Scripts

Below are minimal but complete examples for Android (Appium with Java) and web (Playwright with TypeScript). Adjust selectors, timeouts, and data‑loading to match your project.

#### 7.2.1 Appium Android Example (Java)


import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.remote.DesiredCapabilities;

import java.net.URL;
import java.util.concurrent.TimeUnit;

public class LoginFlowTest {

    private AppiumDriver<MobileElement> driver;

    @Before
    public void setUp() throws Exception {
        DesiredCapabilities caps = new DesiredCapabilities();
        caps.setCapability("platformName", "Android");
        caps.setCapability("deviceName", "Pixel_4_API_33");
        caps.setCapability("appPackage", "com.example.myapp");
        caps.setCapability("appActivity", ".ui.login.LoginActivity");
        caps.setCapability("automationName", "UiAutomator2");
        driver = new AndroidDriver<>(new URL("http://127.0.0.1:4723/wd/hub"), caps);
        driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
    }

    @After
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }

    @Test
    public void testValidLogin() {
        // Precondition: ensure test user exists (could be done via API before suite)
        driver.findElement(By.id("email_input")).sendKeys("alice@example.com");
        driver.findElement(By.id("password_input")).sendKeys("Secure12!");
        driver.findElement(By.id("sign_in_button")).click();

        // Expected: dashboard appears within 5 seconds
        MobileElement dashboard = driver.waitForElement(By.id("dashboard_header"), 5);
        assert dashboard.isDisplayed();
        assert dashboard.getText().equals("Welcome, Alice");
    }
}

Notes

#### 7.2.2 Playwright Web Example (TypeScript)


import { test, expect } from '@playwright/test';

test.describe('Login Flow', () => {
  test('valid credentials redirect to dashboard', async ({ page }) => {
    // Precondition: ensure test user exists via API fixture (omitted for brevity)
    await page.goto('https://app.example.com/login');
    await page.fill('#email', 'alice@example.com');
    await page.fill('#password', 'Secure12!');
    await page.click('button:has-text("Sign In")');

    // Expect navigation to dashboard
    await expect(page).toHaveURL(/.*\/dashboard/, { timeout: 5000 });
    const welcome = page.locator('#welcome-message');
    await expect(welcome).toBeVisible();
    await expect(welcome).toHaveText(/Welcome, Alice/i);
  });

  test('invalid email shows error', async ({ page }) => {
    await page.goto('https://app.example.com/login');
    await page.fill('#email', 'notanemail');
    await page.fill('#password', 'anything');
    await page.click('button:has-text("Sign In")');

    const error = page.locator('.error-message');
    await expect(error).toBeVisible();
    await expect(error).toHaveText(/Please enter a valid email address/);
  });
});

Notes

7.3 Data‑Driven Execution

Both frameworks support iterating over a data set.

Example Playwright data‑driven block:


const loginCases = [
  { email: 'alice@example.com', password: 'Secure12!', shouldPass: true },
  { email: 'bob@example.com', password: 'wrong', shouldPass: false },
  // … add more from your matrix
];

test.describe('Data‑driven login', () => {
  for (const { email, password, shouldPass } of loginCases) {
    test(`login with ${email} – ${shouldPass ? 'pass' : 'fail'}`, async ({ page }) => {
      await page.goto('https://app.example.com/login');
      await page.fill('#email', email);
      await page.fill('#password', password);
      await page.click('button:has-text("Sign In")');

      if (shouldPass) {
        await expect(page).toHaveURL(/.*\/dashboard/);
      } else {
        const error = page.locator('.error-message');
        await expect(error).toBeVisible();
      }
    });
  }
});

7.4 Keeping Manual and Automated Cases in Sync

8. Leveraging Autonomous Exploration with SUSA

While well‑crafted manual and automated tests

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free