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?
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()
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:
-
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.
-
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']")
-
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.
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?
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?
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()
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:
-
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.
-
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']")
-
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.
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
orWebDriverWait
– 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?