VALIDATION LAB•KEYBOARD KEYS
Forms
Keyboard Keys
Test how real users type, navigate, edit and break modern forms.
Scenario
Recruiters and hiring managers recognize shipping-grade QA: keyboard flows, debounce, masks, and OTP are where automation specs diverge from real user behavior.
Section 1 · Live input playground
One surface to watch typing, selection, paste, debounced validation, and caret behavior in real time.
Section 2 · Real QA input modules
Patterns production teams ship — each one is a keyboard and focus trap in waiting.
1. OTP input
Six boxes · auto-advance on digit
2. Search bar
Debounced fetch · spinner while “loading”
3. Password field
Strength meter · caps lock probe
Too weak · score 0/4 (challenge needs ≥3)
4. Phone input mask
Formats as you type · US-style demo
Digits captured: 0/10
5. Disabled input
Cannot type — verify focus skips and screen readers don’t lie.
Section 3 · Common bugs found by QA
- Backspace skips OTP field
- Paste blocked unexpectedly
- Search spams API requests
- Cursor jumps in masked input
- Tab order broken
- Enter submits too early
Section 4 · Playwright automation examples
await page.keyboard.type('Playwright');
await page.keyboard.press('Tab');
await page.keyboard.press('Control+A');Section 5 · Mini challenge
Complete all four — reflects how multi-field forms are really tested.
- Fill OTP — all six digits
- Search — type something containing "Playwright" and wait for results
- Password — reach strong (≥3/4 on meter)
- Phone — full ten-digit mask
Live widget · interact freely
Manual test checklist
- 1Playground debounce flips validation after pause
- 2OTP advances focus on each digit; backspace moves backward
- 3Search shows loading then results (not one request per key)
- 4Password meter reacts; caps warning can appear with Caps Lock
- 5Phone mask keeps cursor sane while digits append
- 6Disabled field rejects typing and looks inert
Expected result
Live playground shows typing metrics, paste count, debounced validation, and caret/selection. Modules: 6-digit OTP with focus advance, search with spinner then delayed mock results, password strength + optional caps warning, masked phone to ten digits, and a disabled field. Mini challenge SUCCESS badge appears only when OTP is full, search includes Playwright with results loaded password meter ≥3/4, and phone mask is complete.
Automation challenge
Complete the mini challenge: fill all OTP cells (`kk-otp-0`…`kk-otp-5`), type a search containing Playwright (`kk-search-input`), satisfy strong password on `kk-password-input` (≥3/4 bars), complete `kk-phone-input` to 10 digits — assert `kk-challenge-complete` is visible.
Stable selectors
- Live playground
[data-testid="kk-section-playground"] - Live input
[data-testid="kk-live-input"] - OTP module
[data-testid="kk-module-otp"] - OTP cell 0
[data-testid="kk-otp-0"] - Search input
[data-testid="kk-search-input"] - Search spinner
[data-testid="kk-search-spinner"] - Password input
[data-testid="kk-password-input"] - Phone input
[data-testid="kk-phone-input"] - Disabled input
[data-testid="kk-disabled-input"] - Common bugs list
[data-testid="kk-section-bugs"] - Playwright examples
[data-testid="kk-section-playwright"] - Mini challenge
[data-testid="kk-section-challenge"] - Challenge success
[data-testid="kk-challenge-complete"]
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. Combine ids or names with OTP indices when multiple inputs share a pattern.
1234
await page.goto('https://lab.hakdogan.com/practice/keyboard-keys');
await page.locator('[name="live-demo"]').fill('abc');
await page.locator('#kk-search-input').fill('Playwright');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
12345678910111213141516171819202122
import { test, expect } from '@playwright/test';
test('keyboard keys lab — multi-field challenge', async ({ page }) => {
await page.goto('https://lab.hakdogan.com/practice/keyboard-keys');
await page.getByTestId('kk-live-input').fill('abc1234567890');
await expect(page.getByTestId('kk-section-playground')).toBeVisible();
await page.getByTestId('kk-search-input').fill('Playwright');
await expect(page.getByTestId('kk-search-results')).toContainText('Playwright', {
timeout: 4000,
});
await page.getByTestId('kk-password-input').fill('Str0ng!Pass');
await page.getByTestId('kk-phone-input').fill('5551234567');
for (let i = 0; i < 6; i++) {
await page.getByTestId(`kk-otp-${i}`).fill(String(i + 1));
}
await expect(page.getByTestId('kk-challenge-complete')).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.
This flow uses keyboard.type() / press() — realistic when debounce, shortcuts, or IME matter.
keyboard-keys.spec.ts- Focus input
- Type "Playwright"
- Press Ctrl+A
- Replace with QA Engineer
- Press End
- Append "Portfolio"
- Press ⌘S
- Assert saved badge