VALIDATION LABCHECKBOXES

Selection

Checkboxes

Toggle individual and bulk checkboxes; verify indeterminate parent state when only some children are selected.

Beginner

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.

1
2
3
4
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.

1
2
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

checkbox.spec.ts
ts
1
2
3
4
5
6
7
8
9
10
11
12
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.

Idlecheckbox.spec.ts
Failure demos
https://lab.hakdogan.com/practice/checkbox
playwright · headed · chromium0.00s
# awaiting Run Test · terminal scrolls automatically
Steps
  1. Checking child A
  2. Asserting indeterminate parent
  3. Checking remaining children
  4. Asserting parent checked
StatusIdle
BrowserChromium
FrameworkPlaywright + TypeScript
Elapsed (live)--
Speccheckbox.spec.ts