VALIDATION LABRADIO BUTTONS

Selection

Radio Buttons

Switch between radio options and validate that exactly one option is selected at any time.

Beginner

Scenario

Pricing pages, plan upgrades, payment method choice — radios drive money. A radio group that allows multiple selections (or none) leaks revenue or breaks trust.

Plan tier

Plan: Starter

Live widget · interact freely

Manual test checklist

  • 1Click each option and confirm only one carries the visual selected state
  • 2Use ↑ / ↓ arrows to navigate the group via keyboard
  • 3Tab into the group — focus should land on the currently selected option
  • 4Reload and confirm a default option exists (no empty state)
  • 5Submit without changing anything — does the form post the default?

Expected result

Only one option carries the selected affordance; the visible plan summary reflects the chosen tier.

Automation challenge

After checking `radio-pro`, assert `radio-pro` is checked AND `radio-starter` is NOT checked in the same test — proves mutual exclusion, not just a positive click.

Stable selectors

  • Plan: Starter[data-testid="radio-starter"]
  • Plan: Pro[data-testid="radio-pro"]
  • Plan: Enterprise[data-testid="radio-enterprise"]
  • Selected plan[data-testid="radio-selected"]

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.

1
2
3
await page.goto('https://lab.hakdogan.com/practice/radio');

await page.locator('#radio-pro').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

radio.spec.ts
ts
1
2
3
4
5
6
7
8
9
10
import { test, expect } from '@playwright/test';

test('radio selection is mutually exclusive', async ({ page }) => {
  await page.goto('https://lab.hakdogan.com/practice/radio');

  await page.getByTestId('radio-pro').check();
  await expect(page.getByTestId('radio-pro')).toBeChecked();
  await expect(page.getByTestId('radio-starter')).not.toBeChecked();
  await expect(page.getByTestId('radio-selected')).toHaveText('Plan: Pro');
});

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.

Idleradio.spec.ts
Failure demos
https://lab.hakdogan.com/practice/radio
playwright · headed · chromium0.00s
# awaiting Run Test · terminal scrolls automatically
Steps
  1. Selecting Pro plan
  2. Asserting Pro is checked
  3. Asserting Starter is unchecked
StatusIdle
BrowserChromium
FrameworkPlaywright + TypeScript
Elapsed (live)--
Specradio.spec.ts