VALIDATION LAB•INPUTS
Forms
Inputs
Validate an email + password login form, surface a red error on invalid email, and assert the success state once both fields satisfy their rules.
Scenario
Text inputs are the #1 source of production bugs: trim, max-length, special chars, autofill, paste, IME composition. A QA engineer who covers them well prevents the silent data-quality issues that pile up in any backend.
Live widget · interact freely
Manual test checklist
- 1Type a malformed email and confirm the inline error fires before submit
- 2Paste 'hasan.qa@hakdogan.com ' with trailing spaces — does the form trim?
- 3Try a password under 8 chars and confirm the rule list highlights what's missing
- 4Use the keyboard only (Tab / Shift-Tab) and confirm focus order is logical
- 5Type emoji and unicode (héllo, 🚀) — does the field accept and submit cleanly?
- 6Disable JavaScript and confirm the form still surfaces native validation
Expected result
Submitting an invalid email reveals 'Please enter a valid email address.'; submitting a valid email and a strong password reveals the green 'Login successful' state.
Automation challenge
Assert the inline error is present BEFORE submit on blur, then write a positive path that drives every password rule from red → green and lands on 'Login successful'. Avoid `waitForTimeout` — use role + state-based locators only.
Stable selectors
- Login form
[data-testid="login-form"] - Work email
[data-testid="work-email"] - Password
[data-testid="login-password"] - Email error
[data-testid="email-error"] - Password rules
[data-testid="input-password-rules"] - Sign in
[data-testid="sign-in-button"] - Result row
[data-testid="input-result"]
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. IDs and name attributes are readable on simple forms; pair them with labels in real apps.
12345
await page.goto('https://lab.hakdogan.com/practice/inputs');
await page.locator('#email').fill('demo@hakdogan.com');
await page.locator('#password').fill('Password123!');
await page.locator('#sign-in').click();Also valid
12
await page.locator('[name="email"]').fill('demo@hakdogan.com');
await page.getByPlaceholder('abc@def.com').fill('demo@hakdogan.com');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
12345678910111213141516171819202122232425262728293031323334353637
import { test, expect } from '@playwright/test';
test.describe('inputs — login validation', () => {
test('invalid email surfaces the inline error', async ({ page }) => {
await page.goto('https://lab.hakdogan.com/practice/inputs');
await page.getByLabel(/work email/i).fill('not-an-email');
await page.getByLabel(/^password$/i).fill('Pass-w0rd!');
await page.getByRole('button', { name: /sign in/i }).click();
await expect(page.getByTestId('email-error')).toHaveText(
'Please enter a valid email address.',
);
await expect(page.getByTestId('input-result')).toHaveAttribute(
'data-state',
'error',
);
});
test('all password rules turn green and login succeeds', async ({ page }) => {
await page.goto('https://lab.hakdogan.com/practice/inputs');
await page.getByLabel(/work email/i).fill('hasan.qa@hakdogan.com');
await page.getByLabel(/^password$/i).fill('Pass-w0rd!');
for (const rule of ['length', 'number', 'upper', 'lower', 'special']) {
await expect(
page.getByTestId(`input-password-req-${rule}`),
).toHaveAttribute('data-state', 'ok');
}
await page.getByRole('button', { name: /sign in/i }).click();
await expect(page.getByTestId('input-result')).toContainText(
'Login successful',
);
});
});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.
This flow uses fill() — fast and stable for standard forms (no per-key events).
inputs.spec.ts- Show login page empty
- Fill Work Email
- Fill Password
- Click Sign in
- Navigate to Bug Playground
- Assert Bug Playground heading
- Pass