VALIDATION LAB•LOGIN FORM
Form Lab
Login Form
Exercise valid login, invalid password, empty fields, remember-me state, and locked account messaging.
Scenario
Login is the most repeated form in a product. QA needs to prove required fields, invalid credentials, persistent remember-me state, and locked account messaging without relying on brittle sleeps.
Live widget · interact freely
Manual test checklist
- 1Submit empty fields and confirm email and password errors
- 2Enter a valid email with a wrong password and confirm invalid password appears
- 3Use locked@lab.dev and confirm the locked account message
- 4Check Remember me and confirm the validation panel records it
- 5Use valid credentials and confirm the success toast
Expected result
Valid credentials show a success toast; wrong passwords and locked accounts show deterministic errors without navigating.
Automation challenge
Cover valid login, invalid password, empty submit, and locked account in separate tests using getByLabel(), getByRole(), getByTestId(), and error assertions.
Stable selectors
- Login form
[data-testid="form-login-form"] - Email
[data-testid="form-login-email"] - Password
[data-testid="form-login-password"] - Remember me
[data-testid="form-login-remember-me"] - Submit
[data-testid="form-login-submit"] - Validation result
[data-testid="form-login-result"] - Toast
[data-testid="form-login-toast"]
Locator strategy
Three levels from simple IDs to scoped Playwright locators. IDs and names are easy to learn but are not always the best long-term choice when labels change or components repeat.
Use simple IDs and names to understand how locating elements works. Form Lab pages expose predictable field ids and data-testid values for every control.
1234
await page.goto('https://lab.hakdogan.com/practice/form-login');
await page.locator('[data-testid="form-login-form"]').waitFor();
await page.locator('[data-testid="form-login-submit"]').click();Avoid as primary strategies
XPath (unless there is no alternative), long CSS chains, Tailwind-style utility class selectors, generated or unstable IDs, and volatile framework internals break when layout, styling, or DOM structure shifts.
12
await page.locator('.w-full.rounded-xl.border.bg-blue-500').fill('demo@example.com');
await page.locator('//div[2]/form/div[1]/input').fill('demo@example.com');Reference Playwright spec
123456789101112131415161718192021222324
import { test, expect } from '@playwright/test';
test.describe('form lab — login form', () => {
test('valid login succeeds', async ({ page }) => {
await page.goto('https://lab.hakdogan.com/practice/form-login');
await page.getByLabel('Email').fill('hasan@lab.dev');
await page.getByLabel('Password').fill('Password123!');
await page.getByLabel(/remember me/i).check();
await page.getByRole('button', { name: /sign in/i }).click();
await expect(page.getByTestId('form-login-toast')).toContainText(/success/i);
});
test('locked account message is visible', async ({ page }) => {
await page.goto('https://lab.hakdogan.com/practice/form-login');
await page.getByLabel('Email').fill('locked@lab.dev');
await page.getByLabel('Password').fill('Password123!');
await page.getByRole('button', { name: /sign in/i }).click();
await expect(page.getByText(/account is locked/i)).toBeVisible();
});
});Headed Test Playback
Simulated headed-browser flow — no real browser is launched.
Automation-style playback (Playwright-shaped logs). No real browser; no commands run on your machine.
form-login.spec.ts- Fill email and password
- Check remember me
- Submit login
- Assert success or locked account state