pythonhtmlselenium-chromedriverbrowser-automation

Why can't Selenium find the element I specified in my code even though it's in the HTML?


I'm learning how to automate navigating through a website using Selenium on Python 3.9.2 using ChromeDriver and I'm stuck at the login page of GitHub, which is the website I'm practicing with.

This is the code I used to automate logging in:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

chrome_options = Options()
chrome_options.add_experimental_option("detach", True)
browser = webdriver.Chrome(chrome_options)
browser.get("https://github.com")`
    
sign_in_link = browser.find_element(By.LINK_TEXT, "Sign in")
sign_in_link.click()
username_box = browser.find_element(By.ID, "login_field")
username_box.send_keys("<username>")
password_box = browser.find_element(By.NAME, "password")
password_box.send_keys("<password>")
password_box.submit()

Now when I execute this code, this is the exception that I get:

selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: {"method":"css selector", "selector":"\[id="login_field"\]"}

I tried replacing the (By.ID, "login_field") with (By.NAME, "login") but I still get the same exception saying it cannot find the element. When I inspect the element on the webpage it shows this:

<input type="text" name="login" id="login_field" class="form-control input-block js-login- field" autocapitalize="off" autocorrect="off" autocomplete="username" autofocus="autofocus" required="required">

I believe I'm using the right arguments for the function find_element() but Selenium cannot find the elements, but if I login manually it works fine. I've also tried using the full XPath as an argument for the function find_element() as follows:

username_box = browser.find_element(By.XPATH, "/html/body/div\[1\]/div\[3\]/main/div/div\[4\]/form/input\[3\]")

which worked the first time I ran the code but when I tried again the same exception was thrown saying the element could not be found. Any ideas on how to successufully automate logging in consistently?


Solution

  • I think the problem of your program is that it tries to fill the input fields right after it opened the page. The login and password elements dont appear immediately, the do a little bit later with the help of javascript. So you want to wait until it happens. Selenium offers the tools for that:

    selenium.webdriver.support.wait.WebDriverWait
    selenium.webdriver.support.expected_conditions
    

    Here is the working version of your code that does not differ that much from the initial one:

    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.support.expected_conditions import element_to_be_clickable
    
    chrome_options = Options()
    chrome_options.add_experimental_option("detach", True)
    browser = webdriver.Chrome(chrome_options)
    browser.get("https://github.com")
    
    sign_in_link = browser.find_element(By.LINK_TEXT, "Sign in")
    sign_in_link.click()
    username_box = WebDriverWait(browser, 10).until(
        element_to_be_clickable((By.ID, "login_field"))
    )
    username_box.send_keys("<username>")
    password_box = browser.find_element(By.NAME, "password")
    password_box.send_keys("<password>")
    password_box.submit()
    

    And here is the version of your code I would actually use:

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.support.expected_conditions import element_to_be_clickable
    
    d = webdriver.Chrome()
    d.get('https://github.com/login')
    
    u = WebDriverWait(d, 10).until
    
    u(element_to_be_clickable((By.CSS_SELECTOR, '#login_field'))).send_keys('<username>')
    password_element = u(element_to_be_clickable((By.CSS_SELECTOR, '#password')))
    password_element.send_keys('<password>')
    password_element.submit()