103

I have a problem - I am using the selenium (firefox) web driver to open a webpage, click a few links etc. then capture a screenshot.

My script runs fine from the CLI, but when run via a cronjob it is not getting past the first find_element() test. I need to add some debug, or something to help me figure out why it is failing.

Basically, I have to click a 'log in' anchor before going to the login page. The construct of the element is:

<a class="lnk" rel="nofollow" href="/login.jsp?destination=/secure/Dash.jspa">log in</a>

I am using the find_element By LINK_TEXT method:

login = driver.find_element(By.LINK_TEXT, "log in").click()

I am a bit of a Python Noob, so I am battling with the language a bit...

A) How do I check that the link is actually being picked up by python? Should I use try/catch block?

B) Is there a better/more reliable way to locate the DOM element than by LINK_TEXT? E.g. In JQuery, you can use a more specific selector $('a.lnk:contains(log in)').do_something();


I have solved the main problem and it was just finger trouble - I was calling the script with incorrect parameters - Simple mistake.

I'd still like some pointers on how to check whether an element exists. Also, an example/explanation of implicit / explicit Waits instead of using a crappy time.sleep() call.

Cheers, ns

ivanleoncz
  • 7,457
  • 4
  • 52
  • 48
nonshatter
  • 3,157
  • 6
  • 22
  • 27
  • You can also find elements by CSS locator or xpath. Tends to be less brittle than by text contents. – Silas Ray Mar 05 '12 at 15:15
  • I believe that both - using content text and XPATH/CSS - are fragile to minor changes in design rather than application logic. A better way would be to find element by either `id`, `class_name` or `name`. – Kshitij Saraogi May 03 '17 at 13:30

12 Answers12

151

a)

from selenium.common.exceptions import NoSuchElementException        
def check_exists_by_xpath(xpath):
    try:
        webdriver.find_element_by_xpath(xpath)
    except NoSuchElementException:
        return False
    return True

b) use xpath - the most reliable. Moreover you can take the xpath as a standard throughout all your scripts and create functions as above mentions for universal use.

UPDATE: I wrote the initial answer over 4 years ago and at the time I thought xpath would be the best option. Now I recommend to use css selectors. I still recommend not to mix/use "by id", "by name" and etc and use one single approach instead.

Alex Okrushko
  • 6,535
  • 5
  • 42
  • 62
  • You should also pass object: browser = webdriver.Firefox() to your custom function –  Jun 27 '14 at 14:08
  • In general, I have rarely come across html pages which conform to the idea of uniform matching. Is there a way to work around mix-matches of the locator methods on such pages? – Kshitij Saraogi May 03 '17 at 13:34
  • @KshitijSaraogi Not sure completely understand your question. If you are staying that it's impossible to have only 'css selectors' in your webdriver tests - it's actually quite possible. I've been doing just that for the last 6 years. – Alex Okrushko May 05 '17 at 20:05
  • @AlexOkrushko What are your reasons for recommending css selectors over xpath? – BoZenKhaa Mar 13 '18 at 14:19
  • 1
    @BoZenKhaa, at least 2 reason: familiarity of css selectors to the devs, which contributes to the readability of the code; xpath had some terrible performance in IE. – Alex Okrushko Mar 16 '18 at 10:20
  • @AlexOkrushko however, i have some reasons using xpath, some website show random css name , i thing using xpath is best way instead css selector at this situation – Lexa Vey Jan 04 '21 at 19:00
93

None of the solutions provided seemed at all easiest to me, so I'd like to add my own way.

Basically, you get the list of the elements instead of just the element and then count the results; if it's zero, then it doesn't exist. Example:

if driver.find_elements_by_css_selector('#element'):
    print "Element exists"

Notice the "s" in find_elements_by_css_selector to make sure it can be countable.

EDIT: I was checking the len( of the list, but I recently learned that an empty list is falsey, so you don't need to get the length of the list at all, leaving for even simpler code.

Also, another answer says that using xpath is more reliable, which is just not true. See What is the difference between css-selector & Xpath? which is better(according to performance & for cross browser testing)?

Brian Leishman
  • 7,334
  • 9
  • 55
  • 86
  • 2
    Is this faster than the above method of Exceptions handling? – Gal Bracha Feb 17 '17 at 10:41
  • 2
    @GalBracha Good question, I'm assuming it would probably depend on the version and platform, so if the speed difference is that important to you here, I would recommend testing it on your target platform for the best results. However, there is a point to be made here about pre-optimizations and the code readability might be more important than the negligable performance improvement in this case – Brian Leishman Feb 17 '17 at 14:00
  • Great solution, but just realized though that this is not working when the input type is hidden. So you should actually use `find_elements_by_id`. Btw `find_element_by_xpath` throws an error when the element is not available – Raunak Thomas Jul 21 '19 at 19:55
  • `find_element_by_xpath` throws an error when it can't find an element because it isn't the plural version of the function – Brian Leishman Jul 22 '19 at 12:19
  • @RaunakThomas also, `find_elements_by_css_selector` doesn't care if an input element has `type="hidden"`, as I'm using it for exactly that in one of my use cases – Brian Leishman Jul 22 '19 at 12:21
  • @BrianLeishman Oh that's strange. For some reason it wasn't working for me and I assumed that it was because of `type="hidden"` but I guess there is something else that I'm missing – Raunak Thomas Jul 26 '19 at 12:17
  • @RaunakThomas sometimes you can't trigger a click with selenium on an element that isn't visible (which you can get around by injecting js that triggers that click) but the css selection just uses the browser's css engine to grab elements, so if you could find it in the Chrome inspector with `document.querySelectorAll()` that's essentially the same thing – Brian Leishman Jul 26 '19 at 12:55
  • 1
    The find-element (singular) form throws an exception when the element is not found. The find-elements (plural with 's') will return an empty list. In my humble opinion all the other examples that are using try-catch to see if an element exist are bad programming style. Try-catch should only be used to catch unexpected situations. If you think the element may not exist your code should be written to handle that situation. – Bill Worthington Mar 08 '21 at 14:37
  • 1
    @BillWorthington agree 100%. I'm not a Python dev so I don't know what's normal around here, but I wouldn't write things that way in any other languages – Brian Leishman Mar 08 '21 at 14:52
  • 1
    @BrianLeishman I was trying to point out your example is the RIGHT way to do it and the OTHER examples were not good programming style. – Bill Worthington Mar 09 '21 at 15:38
  • Just as I was thinking: "try..catch... really? Why can't I just test the result?". Then I find your answer. Thanks – Greg Woods Apr 20 '21 at 22:52
67

A) Yes. The easiest way to check if an element exists is to simply call find_element inside a try/catch.

B) Yes, I always try to identify elements without using their text for 2 reasons:

  1. the text is more likely to change and;
  2. if it is important to you, you won't be able to run your tests against localized builds.

solution either:

  1. You can use xpath to find a parent or ancestor element that has an ID or some other unique identifier and then find it's child/descendant that matches or;
  2. you could request an ID or name or some other unique identifier for the link itself.

For the follow up questions, using try/catch is how you can tell if an element exists or not and good examples of waits can be found here: http://seleniumhq.org/docs/04_webdriver_advanced.html

whale_steward
  • 1,858
  • 2
  • 24
  • 37
Sam Woods
  • 1,790
  • 14
  • 13
  • One use I have for finding text is to see whether a successful/unsuccessful flash message is displayed. Not sure if there's a better way to do it? – Rob Grant Oct 06 '15 at 14:34
  • Those are absolutely excellent reasons to avoid selecting by text – kevlarr Jan 11 '18 at 16:30
  • This was helpful, note that you can now use is_displayed(): https://www.selenium.dev/documentation/webdriver/elements/information/ – Raoul Apr 27 '22 at 09:10
23

Solution without try&catch and without new imports:

if len(driver.find_elements_by_id('blah')) > 0: #pay attention: find_element*s*
    driver.find_element_by_id('blah').click #pay attention: find_element
Super Mario
  • 828
  • 8
  • 16
  • 2
    You're finding it twice, it would certainly be more efficient to store the collection, and if it's truthy, click the first, e.g. `e = driver.find_elements_by_id('blah') if e: e[0].click` – Brian Leishman Mar 21 '19 at 20:22
  • 1
    @BrianLeishman I agree yours way it's more efficient, but I think mine more easy to read and understand – Super Mario Mar 23 '19 at 10:14
  • 3
    Good solution, but I agree with, Brian, regarding `you're finding twice`. – ivanleoncz Jun 11 '19 at 21:08
  • I suppose : `if len(driver.find_elements_by_id('blah')): #do something` should also do the same thing. (`> 0`) is not required. – atb00ker Feb 17 '20 at 20:52
  • @atb00ker you can also just get rid of the `len`, since an empty set in Python is falsey, and a non empty set is truthy, but that's the point of my answer essentially – Brian Leishman Jan 18 '21 at 13:08
8

The same as Brian, but add to this answer from tstempko:

https://sqa.stackexchange.com/questions/3481/quicker-way-to-assert-that-an-element-does-not-exist

So I tried and it works quickly:

driver.implicitly_wait(0)

if driver.find_element_by_id("show_reflist"):        
 driver.find_element_by_id("show_reflist").find_element_by_tag_name("img").click()

after this I restore my default value

driver.implicitly_wait(30)
8-Bit Borges
  • 8,924
  • 25
  • 82
  • 157
vmk
  • 107
  • 1
  • 7
  • yes! if you know the page is already loaded, you dont want to wait around for 30 seconds to get your "doesnt exist return". this returns immediately, just what I needed. – welch May 11 '18 at 06:32
4

You could also do it more concisely using

driver.find_element_by_id("some_id").size != 0
codeslord
  • 1,796
  • 11
  • 16
2

driver.find_element_by_id("some_id").size() is class method.

What we need is :

driver.find_element_by_id("some_id").size which is dictionary so :

if driver.find_element_by_id("some_id").size['width'] != 0 : print 'button exist'

Robert
  • 5,231
  • 43
  • 62
  • 114
Lopi Dani
  • 29
  • 2
  • this is not always right, the width can be zero if element exist also. el = driver.find_element_by_class_name('gNO89b') el.size this is a valid element in google search page but the width is 0 – codeslord Oct 06 '20 at 16:23
1

you could use is_displayed() like below

res = driver.find_element_by_id("some_id").is_displayed()
assert res, 'element not displayed!'
Rajesh Selvaraj
  • 121
  • 2
  • 4
1

When you know the element could not be there, the implicit wait could be a problem. I've created a simple context manager to avoid those waiting times

class PauseExplicitWait(object):
    """
    Example usage:

    with PauseExplicitWait(driver, 0):
        driver.find_element(By.ID, 'element-that-might-not-be-there')
    """
    def __init__(self, driver, new_wait=0):
        self.driver = driver
        self.original_wait = driver.timeouts.implicit_wait
        self.new_wait = new_wait
      
    def __enter__(self):
        self.driver.implicitly_wait(self.new_wait)
  
    def __exit__(self, exc_type, exc_value, exc_tb):
        self.driver.implicitly_wait(self.original_wait)
  
kiril
  • 4,298
  • 1
  • 26
  • 36
1

With the latest Selenium, you can now use you can now use .is_displayed():

https://www.selenium.dev/documentation/webdriver/elements/information/

Raoul
  • 1,612
  • 1
  • 12
  • 13
0

You can find elements by available methods and check response array length if the length of an array equal the 0 element not exist.

element_exist = False if len(driver.find_elements_by_css_selector('div.eiCW-')) > 0 else True
0

You can check by find_elements, if the result is null, that element is not exist

if driver.find_elements(By.SOMETHING, "#someselector") == []:
    continue   # that element is not exist
bao.le
  • 106
  • 2