Python selenium does not return the correct element

Question:

I am new in selenium.

I want to run the automation test for calendar with python selenium, but the return does not same as my expect

The website for testing: https://letcode.in/calendar

I want to select the date in the right calendar.
the right calendar

So I create the variable for the right calendar:
calendarInside = driver.find_element(By.XPATH,"/html/body/app-root/app-calender/section[1]/div/div/div[1]/div/div/div[1]/div[2]")

But when I filter all elements inside the calendarInside with
allDays = calendarInside.find_elements(By.XPATH, "//div[@class='datepicker-date is-current-month']")
the return will include the elements from the left calendar.

How can I select and filter only the element in the right calendar?

The result show in both 2 calendars like image
Result of code

The selenium version: selenium 4.9.1
Full code demo, you can run it to see the problem:

import time

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager


def testCalendar():
    driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
    driver.maximize_window()
    driver.implicitly_wait(10)
    driver.get("https://letcode.in/calendar")
    calendarInside = driver.find_element(By.XPATH,"/html/body/app-root/app-calender/section[1]/div/div/div[1]/div/div/div[1]/div[2]")
    calendarInside.find_element(By.CLASS_NAME, "datetimepicker-clear-button").click()
    calendarInside.find_element(By.CSS_SELECTOR, ".datetimepicker-dummy-input.is-datetimepicker-range").click()
    dayExpected = [6, 25]
    allDays = calendarInside.find_elements(By.XPATH, "//div[@class='datepicker-date is-current-month']")
    for dayfrom in allDays:
        print("dayfrom: " + str(dayfrom.text) + " - expected: " + str(dayExpected[0]))
        if str(dayfrom.text) == str(dayExpected[0]):
            dayfrom.click()
            break
    time.sleep(2)
    
    allDays = calendarInside.find_elements(By.XPATH, "//div[@class='datepicker-date is-current-month']")
    for dayto in allDays:
        print("dayto: " + str(dayto.text) + " - expected: " + str(dayExpected[1]))
        if str(dayto.text) == str(dayExpected[1]):
            dayto.click()
            break
    
    time.sleep(5)
    driver.quit()

def main():
    testCalendar()

if __name__ == "__main__":
    main()

How can I select and filter only the element in the right calendar?
Could you give me some idea for that?

Asked By: NovLe

||

Answers:

try updated code below, your locators were not correct the locatos you have added were for the first date picker

Also this click should be done before to show the datepicker not after

calendarInside.find_element(By.CSS_SELECTOR, ".datetimepicker-dummy-input.is-datetimepicker-range").click()

Full code

   import time

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager


def testCalendar():
    driver = webdriver.Chrome(service=ChromeService(ChromeDriverManager().install()))
    driver.maximize_window()
    driver.implicitly_wait(10)
    driver.get("https://letcode.in/calendar")
    rangeInput = driver.find_element(By.CSS_SELECTOR, ".datetimepicker-dummy-input.is-datetimepicker-range")
    rangeInput.click()
    calendarInside = driver.find_element(By.XPATH,
                                         '//div[contains(@class,"datetimepicker")  and contains(@class,'
                                         '"is-datetimepicker-default")]')
    calendarInside.find_element(By.XPATH, '//button[text()="Clear"]').click()
    dayExpected = [6, 25]

    allDays = calendarInside.find_elements(By.XPATH,
                                           "//div[@class='datepicker is-active']//div[@class='datepicker-date is-current-month']//button")
    for dayfrom in allDays:
        print("dayfrom: " + str(dayfrom.text) + " - expected: " + str(dayExpected[0]))
        if str(dayfrom.text) == str(dayExpected[0]):
            dayfrom.click()
            break
    time.sleep(2)

    allDays = calendarInside.find_elements(By.XPATH, "//div[@class='datepicker is-active']//div[@class='datepicker-date is-current-month']//button")
    for dayto in allDays:
        print("dayto: " + str(dayto.text) + " - expected: " + str(dayExpected[1]))
        if str(dayto.text) == str(dayExpected[1]):
            dayto.click()
            break

    time.sleep(5)
    driver.quit()


def main():
    testCalendar()


if __name__ == "__main__":
    main()
Answered By: Abhay Chaudhary

you may try this way:

import time
from selenium.webdriver import Chrome, ChromeOptions
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
import selenium.webdriver.support.expected_conditions as EC

def testCalendar():

    options = ChromeOptions()
    options.add_argument('--start-maximized')
    driver = Chrome(options=options)

    wait = WebDriverWait(driver, 10)
    driver.get("https://letcode.in/calendar")

    right_calendar = wait.until(EC.visibility_of_all_elements_located((By.CSS_SELECTOR, 'div[class="column"]')))[1]

    right_calendar.find_element(By.CSS_SELECTOR, 'button.datetimepicker-clear-button').click()
    right_calendar.find_element(By.CSS_SELECTOR, 'input.datetimepicker-dummy-input.is-datetimepicker-range').click()

    dayExpected = [6, 25]

    allDays = right_calendar.find_elements(By.CSS_SELECTOR, 'div.datepicker-date.is-current-month')

    for dayfrom in allDays:
        print("dayfrom: " + str(dayfrom.text) + " - expected: " + str(dayExpected[0]))
        if dayfrom.text == str(dayExpected[0]):
            dayfrom.click()
            break
    time.sleep(1)

    allDays = right_calendar.find_elements(By.CSS_SELECTOR, 'div.datepicker-date.is-current-month')
    for dayto in allDays:
        print("dayto: " + str(dayto.text) + " - expected: " + str(dayExpected[1]))
        if dayto.text == str(dayExpected[1]):
            dayto.click()
            break

    time.sleep(5)

if __name__ == "__main__":
    testCalendar()

output:

dayfrom: 1 - expected: 6
dayfrom: 2 - expected: 6
dayfrom: 3 - expected: 6
dayfrom: 4 - expected: 6
dayfrom: 5 - expected: 6
dayfrom: 6 - expected: 6
dayto: 1 - expected: 25
dayto: 2 - expected: 25
dayto: 3 - expected: 25
dayto: 4 - expected: 25
dayto: 5 - expected: 25
dayto: 6 - expected: 25
dayto: 7 - expected: 25
dayto: 8 - expected: 25
dayto: 9 - expected: 25
dayto: 10 - expected: 25
dayto: 11 - expected: 25
dayto: 12 - expected: 25
dayto: 13 - expected: 25
dayto: 14 - expected: 25
dayto: 15 - expected: 25
dayto: 16 - expected: 25
dayto: 17 - expected: 25
dayto: 18 - expected: 25
dayto: 19 - expected: 25
dayto: 20 - expected: 25
dayto: 21 - expected: 25
dayto: 22 - expected: 25
dayto: 23 - expected: 25
dayto: 24 - expected: 25
dayto: 25 - expected: 25

[UPDATE]:

There is not much difference between your code and my code. The only difference is in the line where you define the variable allDays.

I used a CSS_SELECTOR while you’ve used the XPATH. And if you want to use only the XPATH, please refer to the explanation below.

Few notes on your code to make it work as expected:

  1. Your code absolutely works fine without any issue but the reason it’s picking the dates from both the right and left calendars is because of the XPATH you’re using.

  2. To fix this so that it only picks the date from within the desired element (calendarInside in your case),

    use:

    calendarInside.find_elements(By.XPATH, "*//div[@class='datepicker-date is-current-month']")

    instead of:

    calendarInside.find_elements(By.XPATH, "//div[@class='datepicker-date is-current-month']")

  3. If you notice the difference is only of a * which makes the given XPATH a child of the element(calendarInside here).

So, you just need to put a * in the XPATH of the variable allDays in your code as explained above and it starts picking the dates only from your desired (right) calendar.

The asterisk (*): Selects all element nodes in the current context.
reference

I hope this is helpful.

Answered By: Ajeet Verma

In full disclosure, I’m the author of the Browserist package. Browserist is lightweight, less verbose extension of the Selenium web driver that makes browser automation even easier. Simply install the package with pip install browserist.

Allow me to simplify your code by unlocking the power of XPath conditions to find elements in the DOM.

from browserist import Browser

RIGHT_CALENDAR_XPATH = "/html/body/app-root/app-calender/section[1]/div/div/div[1]/div/div/div[1]/div[2]/nwb-date-picker"

def open_calendar(browser: Browser) -> None:
    browser.click.button(f"{RIGHT_CALENDAR_XPATH}/div[2]/div[1]/div/input[1]")

def select_date_in_open_calendar(date: int, browser: Browser) -> None:
    browser.click.button(f"{RIGHT_CALENDAR_XPATH}//div[contains(@class, 'datepicker-date is-current-month')]/button[contains(text(), '{date}')]")

with Browser() as browser:
    browser.open.url("https://letcode.in/calendar")
    open_calendar(browser)
    select_date_in_open_calendar(5, browser)
    select_date_in_open_calendar(26, browser)

Notes:

  • XPath can have built in functions and conditions. First, I search for dates with the is-current-month class – to avoid multiple fields with the same date, e.g. 1 or 30, from previous or next month. Then I pick the element that contains the relevant date number.
  • Browserist doesn’t need explicit conditions like expected_conditions or WebDriverWait – it’s already built in so you don’t have to worry about it. Instead, Browserist awaits an element to be fully loaded before it interacts with it. More on this concept here.

Here’s what I get (slowed down as Browserist finishes the job almost in a blink of an eye). I hope this is helpful. Let me know if you have questions?

Screen capture

Answered By: Jakob Bagterp