VALIDATION LAB•CHECKBOXES
Selection
Checkboxes
Toggle individual and bulk checkboxes; verify indeterminate parent state when only some children are selected.
Scenario
Indeterminate parent state is one of the most-missed accessibility bugs. Bulk-action tables (suspend users, archive orders) silently apply to the wrong rows when the parent state lies.
Parent state: none selected
Live widget · interact freely
Manual test checklist
- 1Tick child A only — parent should appear `indeterminate`, not unchecked
- 2Tick all children — parent should be checked
- 3Untick the parent while children are checked — confirm cascade behavior
- 4Use Space on a focused checkbox — same as a mouse click
- 5Confirm `aria-checked='mixed'` is exposed when indeterminate
Expected result
Selecting all children flips the parent to checked; partial selection sets parent to indeterminate.
Automation challenge
Drive the indeterminate state programmatically from `chk-a` only and assert `[data-testid=chk-state]` reads `indeterminate`. Then check all children and assert the parent is `toBeChecked()`.
Stable selectors
- Parent checkbox
[data-testid="chk-all"] - Child A
[data-testid="chk-a"] - Child B
[data-testid="chk-b"] - Child C
[data-testid="chk-c"] - State readout
[data-testid="chk-state"]
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. Checkbox inputs often expose stable ids per row.
1234
await page.goto('https://lab.hakdogan.com/practice/checkbox');
await page.locator('#chk-a').check();
await page.locator('#chk-all').check();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
123456789101112
import { test, expect } from '@playwright/test';
test('parent checkbox reflects child state', async ({ page }) => {
await page.goto('https://lab.hakdogan.com/practice/checkbox');
await page.getByTestId('chk-a').check();
await expect(page.getByTestId('chk-state')).toContainText('indeterminate');
await page.getByTestId('chk-b').check();
await page.getByTestId('chk-c').check();
await expect(page.getByTestId('chk-all')).toBeChecked();
});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.
checkbox.spec.ts- Checking child A
- Asserting indeterminate parent
- Checking remaining children
- Asserting parent checked