Date pickers are the most inconsistent UI component across applications. Some use native input[type="date"]. Some use full calendar widgets. Some use separate day/month/year dropdowns. Some use a text input with a date format mask. Each requires a different strategy.
import { test, expect } from '@playwright/test';
test('native date input', async ({ page }) => {
await page.goto('/topics/datepickers');
// Native input[type="date"] accepts YYYY-MM-DD format
await page.getByLabel('Start Date').fill('2026-05-09');
await expect(page.getByLabel('Start Date')).toHaveValue('2026-05-09');
});
test('native datetime-local input', async ({ page }) => {
await page.goto('/topics/datepickers');
// datetime-local accepts YYYY-MM-DDTHH:MM format
await page.getByLabel('Appointment').fill('2026-05-09T14:30');
await expect(page.getByLabel('Appointment')).toHaveValue('2026-05-09T14:30');
});import { test, expect } from '@playwright/test';
test('calendar date picker -- same month', async ({ page }) => {
await page.goto('/topics/datepickers');
// Open the calendar
await page.getByLabel('Travel Date').click();
// Wait for calendar to appear
await expect(page.getByRole('dialog')).toBeVisible();
// Click the desired date
await page.getByRole('gridcell', { name: '15' }).click();
await expect(page.getByLabel('Travel Date')).toHaveValue(/15/);
});
test('calendar date picker -- navigate to future month', async ({ page }) => {
await page.goto('/topics/datepickers');
await page.getByLabel('Travel Date').click();
// Click next month button 3 times to go 3 months ahead
for (let i = 0; i < 3; i++) {
await page.getByRole('button', { name: /next month/i }).click();
}
// Select a date in the future month
await page.getByRole('gridcell', { name: '20' }).click();
});
test('calendar date picker -- navigate by month and year dropdowns', async ({ page }) => {
await page.goto('/topics/datepickers');
await page.getByLabel('Birth Date').click();
// Some calendars have month/year dropdowns
await page.getByLabel('Select month').selectOption('January');
await page.getByLabel('Select year').selectOption('1995');
await page.getByRole('gridcell', { name: '15' }).click();
await expect(page.getByLabel('Birth Date')).toHaveValue(/01\/15\/1995/);
});import { test, expect } from '@playwright/test';
test('date as formatted text input', async ({ page }) => {
await page.goto('/topics/datepickers');
const dateInput = page.getByLabel('Check-in Date');
// Clear existing value first
await dateInput.clear();
// Type the date in the expected format
await dateInput.fill('05/09/2026');
// Press Tab or Enter to trigger validation
await page.keyboard.press('Tab');
await expect(dateInput).toHaveValue('05/09/2026');
});
test('date input with masking -- type character by character', async ({ page }) => {
await page.goto('/topics/datepickers');
const dateInput = page.getByLabel('Event Date');
await dateInput.click();
// Some masked inputs need keyboard.type() instead of fill()
await page.keyboard.type('05092026');
await expect(dateInput).toHaveValue('05/09/2026');
});When nothing else works -- the calendar is overly complex, navigation is buggy, or the date is far in the past -- you can set the value directly via JavaScript. This is a last resort. It bypasses user interaction, so use it only when the calendar itself is not what you are testing.
import { test, expect } from '@playwright/test';
test('set date via JavaScript as last resort', async ({ page }) => {
await page.goto('/topics/datepickers');
// Set the value directly
await page.getByLabel('Birth Date').evaluate(
(el: HTMLInputElement) => {
el.value = '1995-01-15';
el.dispatchEvent(new Event('change', { bubbles: true }));
}
);
await expect(page.getByLabel('Birth Date')).toHaveValue('1995-01-15');
});| Date Picker Type | Strategy | Method |
|---|---|---|
| input[type="date"] | Fill with YYYY-MM-DD | fill('2026-05-09') |
| Calendar widget (current month) | Click the day cell | getByRole('gridcell', { name: '15' }) |
| Calendar widget (future month) | Navigate then click | Click next/prev buttons, then click day |
| Text input with format | Type in expected format | fill('05/09/2026') or keyboard.type() |
| Masked input | Type digits only | keyboard.type('05092026') |
| Complex or distant dates | JavaScript evaluation | evaluate() + dispatchEvent |
Always verify the date after selection. Different locales format dates differently -- MM/DD/YYYY vs DD/MM/YYYY. Use a regex or partial match if the exact format varies: toHaveValue(/2026.*05.*09/).
For birth date fields that need navigating 30+ years back, do not click the back arrow 360 times. Look for a year dropdown or use the JavaScript evaluation approach. Your test should finish in seconds, not minutes.
Q: How do you automate date pickers in Playwright?
A: It depends on the implementation. For native input[type="date"], I use fill() with YYYY-MM-DD format. For calendar widgets, I open the calendar, navigate to the right month using next/previous buttons, and click the day cell. For text inputs with date format, I type the date in the expected format. For masked inputs, I use keyboard.type() to trigger the mask formatting. As a last resort for complex calendars, I set the value via JavaScript evaluate(). The key is inspecting the DOM first to understand which type of date picker you are dealing with.
Key Point: Inspect the date picker type first. Native input uses fill(). Calendar widgets need click navigation. Text inputs need the right format. JavaScript evaluate() is the last resort.