VALIDATION LABTABS

Navigation

Tabs

Switch between three tabs, verify only the active panel renders, and confirm the URL hash updates.

Beginner

Scenario

Tabbed UIs (settings, profile, ticket detail) leak bugs around: lazy-mounted vs eagerly-mounted panels, URL hash sync for deep-links, and aria-selected state for screen readers.

Overview

Headline metrics — pass rate, MTTR, open defects. Updated every minute.

Live widget · interact freely

Manual test checklist

  • 1Click each tab — the previous panel unmounts (or hides), the new one mounts
  • 2Reload on `#logs` — Logs tab is selected without a flash of Overview
  • 3Tab + ←/→ — keyboard navigation works between tabs
  • 4Each tab has `role='tab'` and `aria-selected='true'` when active
  • 5Confirm the panel exposes `role='tabpanel'`

Expected result

Clicking 'Logs' shows the Logs panel and writes `#logs` to the URL; reloading lands you back on Logs.

Automation challenge

Click `tab-logs`, assert the URL contains `#logs` AND the panel is visible. Reload on `https://…/practice/tabs#logs` — assert Logs is still active without an extra click.

Stable selectors

  • Overview tab[data-testid="tab-overview"]
  • Logs tab[data-testid="tab-logs"]
  • Settings tab[data-testid="tab-settings"]
  • Active panel[data-testid="tab-panel-active"]

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/tabs');

await page.locator('#tab-logs').click();

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

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

test('tab switch syncs URL hash', async ({ page }) => {
  await page.goto('https://lab.hakdogan.com/practice/tabs');

  await page.getByTestId('tab-logs').click();
  await expect(page).toHaveURL(/#logs$/);
  await expect(page.getByTestId('tab-panel-active')).toContainText('Logs');
});

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.

Idletabs.spec.ts
Failure demos
https://lab.hakdogan.com/practice/tabs
playwright · headed · chromium0.00s
# awaiting Run Test · terminal scrolls automatically
Steps
  1. Interacting with tabs widget
  2. Awaiting deterministic render
  3. Asserting expected state
  4. Cleaning up context
  5. Pass
StatusIdle
BrowserChromium
FrameworkPlaywright + TypeScript
Elapsed (live)--
Spectabs.spec.ts