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.
“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
Date Range Picker Interaction Flow
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.
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.
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.
Basic Date Range Selection via Click
StraightforwardGoal
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:3000with a date range picker component rendered - Clock frozen to April 5, 2026
- No min/max constraints configured
Playwright Implementation
What to Assert Beyond the UI
- The
onChangecallback 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();
});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.
Timezone Boundary Verification
ComplexGoal
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
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
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.
Locale-Aware Date Input and Display
ModerateGoal
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
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();
});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.
Min/Max Constraint Enforcement
ModerateGoal
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
What to Assert Beyond the UI
- Disabled cells have
aria-disabled="true"and do not havetabindex="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('');
});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.
Preset Range Buttons
ModerateGoal
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
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
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 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.
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
How to Test Airtable Form Embed
Step-by-step guide to testing embedded Airtable forms with Playwright. Covers iframe...
How to Test Copy Button
Step-by-step guide to testing code block copy buttons with Playwright. Clipboard API...
How to Test Combobox Multiselect
A practical guide to testing combobox multiselect components with Playwright. Covers...
Ready to automate your testing?
Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.