Practice Widgets Calendar / date picker
2-month range
Widget practice · Date range

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.

id=trigger-depart · trigger-return · picker · prev-month · next-month data-testid=trigger-depart / trigger-return / date-picker / prev-month / next-month / search-flights day cells=role=button · aria-label="Wednesday, May 7 2026" · data-date=YYYY-MM-DD
No search yet — pick dates and click Search flights.

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 field

Single trigger. Opens an inline picker. Selecting a date closes it and shows the value with day-of-week.

id=oneway-trigger · oneway-picker data-testid=oneway-trigger · oneway-value · oneway-picker · oneway-prev · oneway-next day cells=role=button · aria-label · data-date
No date picked yet.

② Range + quick-pick chips + min/max constraint

No past dates

Past days are disabled. Max range = +180 days. Chips below jump straight to a common range without opening the calendar.

data-testid=qp-tomorrow · qp-weekend · qp-next-week · qp-next-month · qp-custom · qp-depart · qp-return · qp-picker constraints=past=disabled · max = +180 days day cell flags=data-disabled="true" on past dates
No range picked yet.

③ 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.

id=native-depart · native-return · native-time data-testid=native-depart · native-return · native-time · native-go fill=ISO 8601 string · YYYY-MM-DD / HH:MM
No values yet.

④ Month / year picker (card expiry style)

No day grid

Two selects: month + year. Common on payment pages. Practise selectOption.

id=exp-month · exp-year data-testid=exp-month · exp-year · exp-check API=locator.selectOption('05') / selectOption({ label: '2027' })
No expiry chosen yet.

⑤ jQuery-UI-style datepicker (text-driven navigation)

.ui-datepicker-title · Next / Prev text

Classic 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.

id=datepicker · jq-picker · jq-prev · jq-next data-testid=datepicker-input · jq-picker · jq-prev · jq-next · ui-datepicker-title class=ui-datepicker · ui-datepicker-title · ui-datepicker-prev · ui-datepicker-next day cells=plain numeric text · click via getByText('16', { exact: true })
No jQuery-style pick yet.
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');