Search code examples
pythonselenium-webdriverweb-scrapingautomation

Python selenium does not return the correct element


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?


Solution

  • 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:

    • You can use XPath with functions and conditions. First, I search for dates with the is-current-month class – to avoid conflicts of multiple fields with the same date, e.g. 1 or 30, from respectively previous or next month.
    • Then I pick the child element that contains the relevant date number with, for instance, /button[contains(text(), '5')].
    • Browserist doesn't need explicit conditions like expected_conditions, WebDriverWait, or driver.implicitly_wait(10) – 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 of Browserist in action