ScreenshotNeo

BlogHow-to

How to Click a Calendar Element Using Cypress

Open a Cypress date picker, select the correct day with stable selectors, handle duplicate dates and overlays, and verify the chosen value reliably.

By the ScreenshotNeo team30 September 20267 min read

How to Click a Calendar Element Using Cypress

Use a stable selector for the date input, click it, scope your query to the calendar, select the intended day with an exact label or date attribute, then start a new Cypress chain for assertions. A typical custom date picker test looks like this:

cy.get('[data-cy="date-input"]').click()
cy.contains('[data-cy="calendar-day"]', /^15$/).click()
cy.get('[data-cy="date-input"]')
  .should('have.value', '2026-09-15')

The selectors in your application will differ. The reliable pattern is always trigger, scope, identify one day, click, and verify the result.

1. Click the date-picker trigger

Give the input or button a selector intended for tests. Dedicated data-* attributes are usually more stable than CSS classes, generated IDs, or styling text.

The Cypress flow: open the picker, choose one uniquely identified day, then verify the value.
The Cypress flow: open the picker, choose one uniquely identified day, then verify the value.
cy.get('[data-cy="date-input"]').click()

Cypress waits for the element to become actionable before clicking. It checks that the element is attached, visible, enabled, and not covered by another element. If the control opens asynchronously, assert that the calendar is visible before searching for a day.

cy.get('[data-cy="date-input"]').click()
cy.get('[data-cy="calendar"]').should('be.visible')

2. Scope the day lookup to the calendar

Unscoped text searches can match dates elsewhere on the page, including a second date picker or a summary label. Scope the query with within() or include the calendar container in the selector.

cy.get('[data-cy="date-input"]').click()
cy.get('[data-cy="calendar"]').within(() => {
  cy.contains('[role="gridcell"]', /^15$/).click()
})

An exact regular expression such as /^15$/ prevents a match on text such as “15 selected” or “15 days”. Cypress commands generally expect one subject. If more than one day matches, make the selector more specific instead of clicking an arbitrary element.

3. Prefer date metadata when day numbers repeat

Many calendars render leading and trailing days from adjacent months. In that case, two cells can display 15. A date attribute is safer than visible text:

Full-date metadata prevents Cypress from clicking a duplicate day from an adjacent month.
Full-date metadata prevents Cypress from clicking a duplicate day from an adjacent month.
cy.get('[data-cy="date-input"]').click()
cy.get('[data-cy="calendar"] [data-date="2026-09-15"]')
  .should('be.visible')
  .click()

cy.get('[data-cy="date-input"]')
  .should('have.value', '2026-09-15')

If your component exposes semantic roles, combine them with the date metadata:

cy.get('[data-cy="calendar"] [role="gridcell"][data-date="2026-09-15"]')
  .click()

4. Verify the selection with a fresh query

A click can close the picker or cause the component to re-render. Do not rely on the old subject after the click. Start a new cy.get() or cy.contains() chain and assert the state your user actually needs.

cy.get('[data-cy="date-input"]').click()
cy.get('[data-cy="calendar"] [data-date="2026-09-15"]').click()

cy.get('[data-cy="date-input"]')
  .should('have.value', '2026-09-15')
cy.get('[data-cy="calendar"]').should('not.be.visible')

Depending on the component, useful assertions include the input value, a selected class, aria-selected="true", the calendar closing, or an updated form summary.

5. Complete custom-calendar examples

Stable data attributes

it('selects September 15, 2026', () => {
  cy.visit('/booking')

  cy.get('[data-cy="date-input"]').click()
  cy.get('[data-cy="calendar"]').should('be.visible')
  cy.get('[data-cy="calendar"] [data-date="2026-09-15"]')
    .should('be.visible')
    .click()

  cy.get('[data-cy="date-input"]')
    .should('have.value', '2026-09-15')
})

Exact day text inside a scoped grid

it('selects a day by its exact label', () => {
  cy.visit('/booking')

  cy.get('[data-cy="date-input"]').click()
  cy.get('[data-cy="calendar"]').within(() => {
    cy.contains('[role="gridcell"]', /^15$/)
      .should('be.visible')
      .click()
  })

  cy.get('[data-cy="date-input"]').should('have.value', '2026-09-15')
})

Choosing a month before selecting the day

When the picker opens on a different month, first use a stable control for next or previous month, then wait for the target month and date cell.

cy.get('[data-cy="date-input"]').click()
cy.get('[data-cy="calendar-month"]')
  .should('contain.text', 'September 2026')
cy.get('[data-cy="calendar"] [data-date="2026-09-15"]').click()

If the test must navigate from an unknown month, model that navigation explicitly with a loop or a component-specific command. Avoid clicking a generic button, because calendars commonly contain previous, next, year, and close buttons.

6. Native HTML date inputs

An <input type="date"> uses a browser-native picker. If the requirement is to test the value contract rather than the native popup UI, enter the Web-standard yyyy-MM-dd value:

cy.get('input[type="date"]')
  .type('2026-09-15')
  .should('have.value', '2026-09-15')

This avoids browser and locale differences. Use the custom-calendar procedure when the purpose of the test is specifically to exercise the visible date-picker interaction.

7. Selector choices and when to use them

Selector Use it when Risk
data-cy or another test attribute You control the markup and want a durable contract Requires adding attributes
data-date="YYYY-MM-DD" The component renders adjacent months or duplicate day numbers Depends on the component exposing date metadata
role="gridcell" plus exact text The calendar has accessible semantic roles Text can still be ambiguous across months
Visible button text The accessible name is stable and unique Labels can change with localization
CSS classes Only when the class is a documented component contract Style refactors can break tests

8. Common failures and fixes

“Element is covered” or “element is not actionable”

Cause: A cookie banner, modal, sticky header, animation, or another layer covers the day cell.

Fix: Dismiss the covering UI, wait for it to disappear, or interact with the calendar as a user would. Keep Cypress’s normal actionability checks enabled. {force: true} skips those checks and can hide a real defect, so use it only when the overlay is intentional and separately verified.

cy.get('[data-cy="cookie-dismiss"]').click()
cy.get('[data-cy="calendar"]').should('be.visible')
cy.get('[data-cy="calendar"] [data-date="2026-09-15"]').click()

“Expected to find element, but never found it”

Cause: The picker has not opened, the day is in another month, or the component uses a different selector.

Fix: Assert the calendar container, inspect the rendered markup, navigate to the correct month, and use the component’s stable attribute. Cypress queries and assertions retry while the UI is loading; arbitrary fixed waits are usually less reliable.

Multiple elements match the day

Cause: Current and adjacent months both contain the same number, or multiple calendars are present.

Fix: Scope to one calendar and select by data-date, month metadata, or an exact accessible label. Use .first() or .eq() only when the ordering is an intentional part of the component contract. multiple: true clicks every match and is normally incorrect for a date selection.

The assertion checks stale content

Cause: The click caused a re-render and the chained subject no longer represents the current DOM.

Fix: Start a new query after the click:

cy.get('[data-cy="calendar-day"]').click()
cy.get('[data-cy="date-input"]').should('have.value', '2026-09-15')

The test passes locally but fails in another browser or locale

Cause: Locale-specific labels, timezone conversion, or a native date-picker UI differs.

Fix: Assert the normalized value or ISO date attribute, set the test data explicitly, and use yyyy-MM-dd for native date inputs. Avoid assertions tied to localized month names unless localization itself is under test.

9. Reliability and performance practices

  • Use one stable selector for the trigger and one for the calendar container.
  • Prefer a full date attribute over a day number when duplicate dates can render.
  • Wait on meaningful UI state such as visibility, the target month, or the date cell’s existence.
  • Keep assertions close to the user outcome: selected value, selected state, or closed picker.
  • Do not add large fixed delays for animations or network requests; Cypress’s retryable queries and assertions provide a faster synchronization point.
  • Keep the calendar markup deterministic in test data so the same date is available in every run.
  • Use a custom Cypress command only after the selector and assertion pattern is stable.
Cypress.Commands.add('chooseDate', (date) => {
  cy.get('[data-cy="date-input"]').click()
  cy.get('[data-cy="calendar"]').should('be.visible')
  cy.get(`[data-cy="calendar"] [data-date="${date}"]`)
    .should('be.visible')
    .click()
  cy.get('[data-cy="date-input"]').should('have.value', date)
})

// Usage
cy.chooseDate('2026-09-15')

10. Or skip the browser setup

If you need an image of the finished page rather than an interaction test, ScreenshotNeo can capture the URL with one request. See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. An MCP server lets Claude, Cursor, and other MCP clients take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account and start with the 1,000 included screenshots.

11. FAQ

Should I use cy.contains() or cy.get()?

Use cy.get() for stable attributes and cy.contains() when the accessible label or exact visible text is the contract. Scope either command to the calendar.

Why does a day number select the wrong month?

Leading or trailing days from adjacent months may share the same number. Select by a full date attribute or include month metadata.

Is force: true a good fix?

Usually no. It bypasses actionability checks, including coverage checks. Remove the overlay or correct the layout so the test follows the real interaction.

How do I test a native date picker?

For input[type="date"], type a valid yyyy-MM-dd value and assert the normalized value. Test a custom picker separately when its visible behavior matters.

Can Cypress select a date that loads after an API request?

Yes. Assert the calendar or target cell state. Cypress retries the query while the application updates, so the assertion becomes the synchronization point.