UI Component Testing Guide

How to Test Date Range Picker with Playwright: Timezones, Locales, and Calendar Grids

A scenario-by-scenario walkthrough of testing date range pickers with Playwright. Timezone drift, locale formatting, keyboard navigation, min/max date constraints, preset ranges, calendar grid traversal, and the subtle bugs that slip past manual QA.

73%

According to the 2025 State of JS survey, date handling remains the most common source of frontend bugs, with 73% of respondents reporting at least one date-related defect in the past year.

State of JS 2025

0Scenarios covered
0+Timezone edge cases
0Locale formats tested
0%Fewer lines with Assrt

Date Range Picker Interaction Flow

UserStart InputCalendar GridEnd InputApplication StateClick start date fieldOpen calendar popoverClick start date cellPopulate start dateClick end date cellPopulate end dateEmit onChange(startDate, endDate)Update UI with selected range

1. Why Testing Date Range Pickers Is Harder Than It Looks

A date range picker appears simple on the surface: two date fields and a calendar popover. In practice, this component sits at the intersection of several of the hardest problems in frontend development. Timezone handling alone introduces an entire class of bugs. A user in Tokyo selecting “April 5” may produce a different UTC timestamp than a user in Los Angeles selecting the same visual date, and your test running in a CI environment with UTC system time will see yet another result. If your tests hard-code expected date strings without controlling the timezone, they will pass locally and fail in CI (or vice versa).

Locale formatting adds a second layer of complexity. The same date renders as “04/05/2026” in the US, “05/04/2026” in the UK, and “2026-04-05” in ISO format. If your test types a date string into an input field, it must match the locale the component expects. Many date picker libraries parse input text according to the browser locale, which means a test that works on your American-locale laptop will reject valid dates when CI runs with a European locale.

Then there are the interaction mechanics. Calendar grids use complex ARIA roles (role="grid", role="gridcell") and keyboard navigation patterns (arrow keys to move between days, Page Up/Down for months, Home/End for week boundaries). Min/max date constraints disable certain cells, and your test needs to verify both that valid dates are selectable and that disabled dates are not. Preset range buttons (“Last 7 days”, “This month”, “Last quarter”) calculate dates dynamically, so assertions must account for the current date at test runtime.

Date Range Picker Complexity Map

🌐

User Input

Click, type, or keyboard

⚙️

Locale Parsing

MM/DD vs DD/MM vs ISO

↪️

Timezone Conversion

Local to UTC boundary

🔒

Constraint Validation

Min/max, disabled dates

⚙️

Range Calculation

Start <= End, presets

State Update

Emit onChange event

Calendar Navigation Flow

🌐

Open Calendar

Click input or icon

↪️

Navigate Month

Prev/Next arrows or PageUp/PageDown

Select Start

Click or Enter on cell

🌐

Highlight Range

Visual hover between dates

Select End

Click or Enter on cell

🔒

Close Calendar

Auto-close or Escape

A robust date range picker test suite needs to control all of these variables simultaneously. The sections below walk through each scenario with runnable Playwright TypeScript, starting from environment setup and progressing through every edge case your users will encounter.

2. Setting Up a Reliable Test Environment

Date range picker tests are uniquely sensitive to environment configuration. A test that passes on your machine but fails in CI almost always traces back to one of three variables: system timezone, browser locale, or the current date itself. Lock all three down before writing a single assertion.

Date Range Picker Test Environment Checklist

  • Set TZ environment variable to a fixed timezone (e.g., America/New_York)
  • Configure browser locale in Playwright context options
  • Use clock.install() to freeze the current date for deterministic tests
  • Create helper functions for date arithmetic relative to the frozen date
  • Set up test fixtures with known min/max date constraints
  • Configure viewport to ensure full calendar grid is visible without scrolling

Playwright Configuration for Date Testing

The most important configuration decision is timezone pinning. Playwright supports the timezoneIdcontext option, which overrides the browser's timezone independently of the system clock. Combine this with the locale option to control how dates render in the UI.

playwright.config.ts

Freezing the Clock

Date range pickers that include preset ranges (“Last 7 days”, “This month”) calculate dates relative to Date.now(). Without a frozen clock, your assertions need to compute expected dates dynamically, which makes test failures harder to debug. Playwright's clock.install() freezes the JavaScript clock at a specific point in time.

test/fixtures/date-helpers.ts
Install Dependencies

3. Scenario: Basic Date Range Selection

The foundational scenario is selecting a start date and end date by clicking calendar cells. This validates the core interaction loop: opening the calendar, clicking a start date, seeing the range highlight appear, clicking an end date, and confirming both values populate the input fields. Despite being the simplest scenario, this is where most test suites discover their first selector issues, because calendar grids vary wildly in their DOM structure across libraries.

1

Basic Date Range Selection via Click

Straightforward

Goal

Open the date range picker, select April 10 as the start date and April 17 as the end date, confirm both input fields show the correct formatted values, and verify the onChange callback fires with the correct Date objects.

Preconditions

  • App running at localhost:3000 with a date range picker component rendered
  • Clock frozen to April 5, 2026
  • No min/max constraints configured

Playwright Implementation

date-range-basic.spec.ts

What to Assert Beyond the UI

  • The onChange callback received Date objects, not strings
  • Start date is at midnight local time (00:00:00), not noon or some other arbitrary hour
  • End date includes the full day (23:59:59 or start of next day, depending on your convention)
  • The range is inclusive on both ends

Basic Date Range Selection

import { test, expect } from '@playwright/test';

test('select a date range', async ({ page }) => {
  await page.clock.install({
    time: new Date('2026-04-05T12:00:00'),
  });
  await page.goto('/date-picker-demo');
  const startInput = page.getByLabel('Start date');
  await startInput.click();
  const calendar = page.getByRole('grid', {
    name: /april 2026/i,
  });
  await expect(calendar).toBeVisible();
  await calendar
    .getByRole('gridcell', { name: '10' })
    .click();
  await calendar
    .getByRole('gridcell', { name: '17' })
    .click();
  await expect(startInput).toHaveValue('04/10/2026');
  const endInput = page.getByLabel('End date');
  await expect(endInput).toHaveValue('04/17/2026');
  await expect(calendar).not.toBeVisible();
});
44% fewer lines

4. Scenario: Timezone Boundary Dates

Timezone bugs in date range pickers are notorious because they only manifest at day boundaries. The classic failure: a user in UTC-5 selects “April 5” and the application stores “2026-04-05T00:00:00-05:00”, which converts to “2026-04-05T05:00:00Z” in UTC. If the backend query uses >= 2026-04-05 and < 2026-04-06 in UTC, it misses records created before 5:00 AM UTC. This off-by-one day error is the single most common date bug in production applications.

2

Timezone Boundary Verification

Complex

Goal

Select a date near midnight in a non-UTC timezone and verify the component emits the correct UTC timestamp. Run the same test across three timezones to confirm consistent behavior.

Playwright Implementation

date-range-timezone.spec.ts

What to Assert Beyond the UI

  • The ISO timestamp stored by the application corresponds to the correct calendar day in the user's timezone, not in UTC
  • Backend API requests include the timezone offset or use start-of-day in the user's local time
  • The displayed date string matches the selected calendar cell regardless of timezone
Cross-Timezone Test Results

Try Assrt for free

Open-source AI testing framework. No signup required.

Get Started

5. Scenario: Locale-Specific Formatting

Date formatting varies dramatically across locales. In en-US, April 5, 2026 renders as “04/05/2026” (month first). In en-GB, the same date renders as “05/04/2026” (day first). In ja-JP, it may render as “2026/04/05”. If your date range picker accepts typed input, a user typing “04/05/2026” in a British locale intends May 4, not April 5. This is a real production bug that affects every internationalized application.

3

Locale-Aware Date Input and Display

Moderate

Goal

Type a date string into the input field using the locale format and verify the component parses it correctly. Run across US, UK, and Japanese locales.

Playwright Implementation

date-range-locale.spec.ts

What to Assert Beyond the UI

  • The parsed Date object is April 10 in every locale, not a different day caused by MM/DD vs DD/MM confusion
  • Invalid locale strings (e.g., “13/32/2026”) show a validation error, not a silent wrong date
  • Calendar highlights the correct cell after typed input

Locale-Aware Date Range Entry

import { test, expect } from '@playwright/test';

test('en-GB locale parses day-first input', async ({
  browser,
}) => {
  const context = await browser.newContext({
    locale: 'en-GB',
  });
  const page = await context.newPage();
  await page.clock.install({
    time: new Date('2026-04-05T12:00:00Z'),
  });
  await page.goto('/date-picker-demo');
  const startInput = page.getByLabel('Start date');
  await startInput.click();
  await startInput.fill('10/04/2026');
  await startInput.press('Tab');
  await expect(startInput).toHaveValue('10/04/2026');
  const iso = await page
    .locator('[data-testid="date-range-value"]')
    .getAttribute('data-start-iso');
  expect(iso).toContain('2026-04-10');
  await context.close();
});
55% fewer lines

6. Scenario: Keyboard-Only Date Entry

Accessibility compliance (WCAG 2.1 AA) requires that every interactive component be fully operable with a keyboard. For date range pickers, this means users must be able to open the calendar with Enter or Space, navigate between days with arrow keys, jump between months with Page Up/Down, and select dates with Enter. The WAI-ARIA Date Picker Dialog pattern defines the full expected keyboard behavior.

4

Full Keyboard Navigation and Selection

Complex

Goal

Without any mouse clicks, open the date range picker, navigate to a target date using only keyboard shortcuts, select a start and end date, and confirm the range is applied.

Playwright Implementation

date-range-keyboard.spec.ts

What to Assert Beyond the UI

  • Focus management: after selecting a date, focus moves to the correct next element (end date input or calendar close)
  • aria-selected is set on the chosen gridcells
  • Escape closes the calendar and returns focus to the trigger input
  • Home and End keys move to the first and last day of the current week

7. Scenario: Min/Max Date Constraints

Most production date range pickers enforce constraints: a booking system might prevent selecting dates in the past, a reporting tool might limit the range to the last 90 days, and a subscription manager might cap the end date at the current billing period. Testing constraints means verifying two things simultaneously: valid dates within the range are selectable, and dates outside the range are properly disabled.

5

Min/Max Constraint Enforcement

Moderate

Goal

Configure a date range picker with a minimum date of April 1, 2026 and a maximum date of April 30, 2026. Verify that dates outside this range are disabled and that clicking them does not change the selection.

Playwright Implementation

date-range-constraints.spec.ts

What to Assert Beyond the UI

  • Disabled cells have aria-disabled="true" and do not have tabindex="0"
  • Typing a date outside the allowed range into the input shows a validation message
  • The previous/next month navigation buttons are disabled when all dates in that month fall outside the constraint range

Min/Max Date Constraints

import { test, expect } from '@playwright/test';

test('disabled dates outside range', async ({ page }) => {
  await page.clock.install({
    time: new Date('2026-04-05T12:00:00'),
  });
  await page.goto(
    '/date-picker-demo?minDate=2026-04-01&maxDate=2026-04-30'
  );
  const startInput = page.getByLabel('Start date');
  await startInput.click();
  await page.keyboard.press('PageUp');
  const marchCell = page
    .getByRole('grid', { name: /march 2026/i })
    .getByRole('gridcell', { name: '31' });
  await expect(marchCell).toHaveAttribute(
    'aria-disabled',
    'true'
  );
  await marchCell.click({ force: true });
  await expect(startInput).toHaveValue('');
});
50% fewer lines

8. Scenario: Preset Range Buttons and Calendar Navigation

Many date range pickers include preset buttons: “Today”, “Last 7 days”, “This month”, “Last quarter”. These buttons are convenient for users but tricky to test because they calculate dates relative to the current moment. If your test does not freeze the clock, the expected values change every day and your assertions break. Even with a frozen clock, you need to calculate the expected range for each preset and account for month boundaries, leap years, and quarter definitions.

6

Preset Range Buttons

Moderate

Goal

Click each preset range button and verify the start and end dates are calculated correctly relative to the frozen clock date of April 5, 2026.

Playwright Implementation

date-range-presets.spec.ts

What to Assert Beyond the UI

  • Preset buttons set both start and end dates atomically (no intermediate state where only one is set)
  • Clicking a preset after manually selecting a range replaces the manual selection completely
  • The calendar view navigates to show the start month of the preset range
Full Test Suite Run

9. Common Pitfalls That Break Date Range Picker Tests

These are real failures sourced from GitHub issues, Stack Overflow threads, and production incident reports. Each one has caused flaky or silently wrong date range picker test suites.

Pitfalls to Watch For

  • Hard-coding date strings instead of computing them relative to a frozen clock. Tests break on the first of every month when day counts change.
  • Selecting a date by text content alone when the calendar grid shows days from adjacent months. Clicking "1" may select the 1st of the previous or next month if the cell is from an overflow row.
  • Not accounting for DST transitions. In the America/New_York timezone, March 8, 2026 has only 23 hours. A "last 24 hours" preset crossing that boundary produces unexpected results.
  • Assuming the calendar always opens to the current month. If a date is pre-selected, many pickers open to the month containing that date instead.
  • Using toLocaleDateString() in assertions without specifying an explicit locale. The output depends on the Node.js ICU data configuration, which varies across CI images.
  • Forgetting that fill() clears the input first, which triggers onChange with an empty value before the new value. Some pickers validate on every keystroke and show an error flash.
  • Testing only left-to-right selection. Users sometimes select the end date first (or a later date first) and expect the picker to auto-sort the range.
  • Not closing the calendar before asserting final state. Some pickers only commit the value to the form model when the popover closes.

Pitfall Deep Dive: Adjacent Month Overflow Cells

Most calendar grids show 42 cells (6 weeks) regardless of how many days the current month has. The remaining cells show dates from the previous or next month, usually in a dimmed style. If your test clicks a gridcell by matching the text “1” without scoping to the current month, Playwright may click the first matching cell, which could be the 1st of the previous month in the overflow area. The fix is to scope your selector.

pitfall-overflow-fix.ts

Pitfall Deep Dive: DST and the Missing Hour

Daylight Saving Time transitions create a class of bugs that only appear twice a year. In the US, clocks spring forward on the second Sunday of March. On March 8, 2026, at 2:00 AM ET, the clock jumps to 3:00 AM. If your date picker stores dates as midnight local time and the test creates a Date object for “2026-03-08T02:30:00” in America/New_York, that time does not exist. JavaScript will silently adjust it to 3:30 AM, which may push the date into an unexpected day if combined with timezone conversion. Always test date ranges that cross DST boundaries.

10. Writing These Scenarios in Plain English with Assrt

Every scenario above required careful consideration of locators, timezone configuration, clock freezing, and assertion timing. With Assrt, you describe the intent of each test in plain English and let the compiler handle the Playwright implementation details. Here is the full date range picker test suite compiled into a single .assrt file.

date-range-picker.assrt

Assrt compiles each scenario block into the same Playwright TypeScript code shown in the sections above, committed to your repository as real tests you can read, run, and modify. The config block at the top sets the frozen date, locale, and timezone once, and each scenario can override those settings individually (as shown in the timezone and locale scenarios).

When a date picker library updates its DOM structure, renames an ARIA label, or changes how overflow cells are marked, Assrt detects the test failure, inspects the new DOM, and opens a pull request with updated selectors. Your scenario files remain untouched. The plain-English descriptions serve as living documentation that any team member can read and understand, regardless of their familiarity with Playwright internals.

Start with the basic selection scenario. Once it passes in CI, add the timezone boundary test, then the locale variants, then keyboard navigation, then min/max constraints, and finally the preset range buttons. Within a single afternoon, you can build complete date range picker coverage that catches the timezone drift, locale confusion, and constraint bypass bugs that slip past manual QA every time.

Related Guides

Ready to automate your testing?

Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.

$npm install @assrt/sdk