Calendar date picker practice
A two-month range picker in the spirit of MakeMyTrip / SpiceJet — click a departure date, then a return date.
Each day cell is a role="button" with a precise aria-label and a data-date attribute,
so you can target dates with getByRole or data-testid-style queries.
More date-picker patterns
Four common variants you'll see on travel + booking sites. Each is built locally with no third-party widget — just enough DOM to practise locators on.
① One-way · single date picker
No return fieldSingle trigger. Opens an inline picker. Selecting a date closes it and shows the value with day-of-week.
Choose date
② Range + quick-pick chips + min/max constraint
No past datesPast days are disabled. Max range = +180 days. Chips below jump straight to a common range without opening the calendar.
Pick travel dates · today → +180 days
③ Native <input type="date">
Browser-rendered
Playwright fills these with an ISO string — fill('2026-06-15'). No need to click cells. Browser-rendered popup; tests skip it entirely.
④ Month / year picker (card expiry style)
No day gridTwo selects: month + year. Common on payment pages. Practise selectOption.
⑤ jQuery-UI-style datepicker (text-driven navigation)
.ui-datepicker-title · Next / Prev textClassic jQuery-UI pattern — single trigger, no data-date attributes, title shows Month Year, navigation by clicking a span containing Next / Prev. Practise a loop that reads the title text and clicks Next until it matches your target month.
getByText('16', { exact: true })
Playwright solution — all 4 extra pickersOne-way · quick-pick range · native input · month-year selects. Show solution
// ① One-way single picker
await page.getByTestId('oneway-trigger').click();
await page.locator('#oneway-picker [data-date="2026-06-15"]').click();
await expect(page.getByTestId('oneway-value')).toContainText('Jun 15');
// ② Quick-pick chip — no calendar open needed
await page.getByTestId('qp-weekend').click();
await expect(page.getByTestId('qp-summary')).toContainText('days');
// ② Custom range with disabled-past-date guard
await page.getByTestId('qp-depart').click();
// click a past date → should not change the value
const past = page.locator('#qp-picker [data-date="2025-01-01"]');
await expect(past).toHaveAttribute('data-disabled', 'true');
await page.locator('#qp-picker [data-date="2026-06-20"]').click();
await page.locator('#qp-picker [data-date="2026-06-27"]').click();
// ③ Native input — fill with ISO string, skip the popup entirely
await page.getByTestId('native-depart').fill('2026-06-15');
await page.getByTestId('native-return').fill('2026-06-20');
await page.getByTestId('native-time').fill('09:30');
await page.getByTestId('native-go').click();
// ④ Month-year selects
await page.getByTestId('exp-month').selectOption('05');
await page.getByTestId('exp-year').selectOption({ label: '2029' });
await page.getByTestId('exp-check').click();
await expect(page.getByTestId('exp-output')).toContainText('05/2029');
// ⑤ jQuery-UI-style — read title text, click Next until it matches target
await page.locator('#datepicker').click();
const target = "July 2026";
while (true) {
const titleText = (await page.locator('.ui-datepicker-title').textContent())?.trim();
if (titleText === target) {
await page.getByText('16', { exact: true }).click();
break;
}
await page.locator('span:has-text("Next")').click();
}
await expect(page.locator('#datepicker')).toHaveValue('16/07/2026');