VALIDATION LABDATE RANGE PICKER

Date & Files

Date Range Picker

Set start/end times, pick day span in two taps, and validate the pill shows `HH:MM - DD/MM/YYYY → …` including auto-swap.

Advanced

Scenario

Booking, analytics filters, payroll periods. Bugs hide in: the inclusive/exclusive end day, the leap-year boundary, the swap-on-pick logic, and the empty 'reset' state.

Pick date & time range

Live widget · interact freely

Manual test checklist

  • 1Pick days 5 and 12 with default clocks — pill shows `HH:MM - 05/MM/YYYY → HH:MM - 12/MM/YYYY`
  • 2Flip end earlier than start (tap 12 then 5) — times stay attached but days swap logically
  • 3Change only `range-start-time` — left side updates, calendar selection unchanged until you pick again
  • 4Reset wipes both anchors but keeps spinner defaults intact

Expected result

First tap is start, second is end; each side carries its clock. If second day < first, endpoints swap.

Automation challenge

Hardcode `range-start-time` / `range-end-time` inputs, tap `range-cell-12` then `range-cell-5`, assert the chip regex shows `/05\/\d{2}\/\d{4}.*→.*12\/` DD segments after swap.

Stable selectors

  • Open range[data-testid="range-open"]
  • Start time[data-testid="range-start-time"]
  • End time[data-testid="range-end-time"]
  • Range chip[data-testid="range-chip"]
  • Range cell template[data-testid="range-cell-{n}"]
  • Reset[data-testid="range-reset"]

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
4
5
await page.goto('https://lab.hakdogan.com/practice/date-range');

await page.locator('#range-open').click();
await page.locator('#range-cell-12').click();
await page.locator('#range-cell-5').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

date-range.spec.ts
ts
1
2
3
4
5
6
7
8
9
10
11
12
13
import { test, expect } from '@playwright/test';

test('date range auto-swaps when end < start', async ({ page }) => {
  await page.goto('https://lab.hakdogan.com/practice/date-range');

  await page.getByTestId('range-open').click();
  await page.getByTestId('range-cell-12').click();
  await page.getByTestId('range-cell-5').click();

  await expect(page.getByTestId('range-chip')).toHaveText(
    /\d{2}:\d{2} - 05\/\d{2}\/\d{4}\s*→\s*\d{2}:\d{2} - 12\/\d{2}\/\d{4}/,
  );
});

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.

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