379

I'm trying to test a complicated JavaScript interface with Selenium (using the Python interface, and across multiple browsers). I have a number of buttons of the form:

<div>My Button</div>

I'd like to be able to search for buttons based on "My Button" (or non-case-sensitive, partial matches such as "my button" or "button").

I'm finding this amazingly difficult, to the extent to which I feel like I'm missing something obvious. The best thing I have so far is:

driver.find_elements_by_xpath('//div[contains(text(), "' + text + '")]')

This is case-sensitive, however. The other thing I've tried is iterating through all the divs on the page, and checking the element.text property. However, every time you get a situation of the form:

<div class="outer"><div class="inner">My Button</div></div>

div.outer also has "My Button" as the text. To fix that, I've tried looking to see if div.outer is the parent of div.inner, but I couldn't figure out how to do that (element.get_element_by_xpath('..') returns an element's parent, but it tests not equal to div.outer).

Also, iterating through all the elements on the page seems to be really slow, at least using the Chrome webdriver.

Ideas?


I asked (and answered) a more specific version here: How can I get text of an element in Selenium WebDriver, without including child element text?

Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
josh
  • 9,038
  • 8
  • 31
  • 37

13 Answers13

480

Try the following:

driver.find_elements_by_xpath("//*[contains(text(), 'My Button')]")
Ricky Sahu
  • 23,455
  • 4
  • 42
  • 32
  • 7
    Thank you for the reply, it was 50% of what I needed (got me started). The form I arrived to is this "(//*[contains(text(), '" + text + "')] | //*[@value='" + text + "'])" it will search for given text not only inside element nodes, but also inside input elements whose text was set via 'value' attribute i.e. . Though do note, the value must be strict match, not just contain the text. – Ivan Koshelev Oct 11 '14 at 20:37
  • 16
    Also worth mentioning for other search engine visitors: if you're looking for a link, there are `find_element(s)_by_link_text` and `find_element(s)_by_partial_link_text` methods – Dan Passaro Nov 17 '14 at 23:45
  • 4
    What if the text is dynamic? That is, might contain quotes. Wouldn't that break this solution? – IcedDante Jul 08 '15 at 17:59
  • 4
    Searching for certain names seems to break this. Take the following for an example: "//*[contains(text(), '"+username+"')]" if username = "O'Reilly"; then the xpath would become invalid. Is there a way around this? – Sakamoto Kazuma Jun 07 '16 at 19:37
  • 3
    It doesn't seem to work when the target text has multiple lines. – Shawn Nov 20 '16 at 18:25
  • 1
    does this work even when the element is not visible on the page? When I try to search like this in Chrome inspect element it doesn't find the element(it does support xpaths). Once i scroll the webpage it is however detected by inspect element search function. – Matej Novosad Jul 14 '19 at 19:59
  • Note that `find_elements_by_xpath` is now deprecated. Use something like `find_element("xpath", "")`. [Source](https://stackoverflow.com/questions/72754651/attributeerror-webdriver-object-has-no-attribute-find-element-by-xpath) – Thierryonre Aug 04 '22 at 15:03
49

In the HTML which you have provided:

<div>My Button</div>

The text My Button is the innerHTML and have no whitespaces around it so you can easily use text() as follows:

my_element = driver.find_element_by_xpath("//div[text()='My Button']")

Note: text() selects all text node children of the context node


Text with leading/trailing spaces

In case the relevant text containing whitespaces either in the beginning:

<div>   My Button</div>

or at the end:

<div>My Button   </div>

or at both the ends:

<div> My Button </div>

In these cases you have two options:

  • You can use contains() function which determines whether the first argument string contains the second argument string and returns boolean true or false as follows:

    my_element = driver.find_element_by_xpath("//div[contains(., 'My Button')]")
    
  • You can use normalize-space() function which strips leading and trailing white-space from a string, replaces sequences of whitespace characters by a single space, and returns the resulting string as follows:

    driver.find_element_by_xpath("//div[normalize-space()='My Button']")
    

XPath expression for variable text

In case the text is a variable, you can use:

foo= "foo_bar"
my_element = driver.find_element_by_xpath("//div[.='" + foo + "']")
Benjamin Loison
  • 3,782
  • 4
  • 16
  • 33
undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
41

You could try an XPath expression like:

'//div[contains(text(), "{0}") and @class="inner"]'.format(text)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
andrean
  • 6,717
  • 2
  • 36
  • 43
  • Thanks... so that helps distinguish inner from outer, but that actually works fine with xpath, I was only having that problem iterating through all the divs. My problem with xpath is I can't figure out how to make it case-insensitive? – josh Sep 07 '12 at 19:33
  • 2
    xpath 2.0 has a lower-case function, so this should work: '//div[contains(lower-case(text()), "{0}")]'.format(text) – andrean Sep 07 '12 at 20:18
  • thanks! although, my understanding is that xpath 2.0 isn't supported across the major browsers... – josh Sep 07 '12 at 21:05
  • selenium evaluates xpath expressions directly with the browser's own methods, so it depends which browser are you using with selenium. generally only ie 6,7 and 8 should not support xpath 2.0. – andrean Sep 07 '12 at 21:14
  • `.format` isn't recognized in my eclipse. it gives and error. any idea, why? – anujin Jul 26 '13 at 07:15
  • format is a standard python string function, if I'm not mistaken it's available from python 2.6, basically a modern replacement for %s style formatting – andrean Jul 29 '13 at 21:19
32

//* will be looking for any HTML tag. Where if some text is common for Button and div tag and if //* is categories it will not work as expected. If you need to select any specific then You can get it by declaring HTML Element tag. Like:

driver.find_element_by_xpath("//div[contains(text(),'Add User')]")
driver.find_element_by_xpath("//button[contains(text(),'Add User')]")
Ishita Shah
  • 3,955
  • 2
  • 27
  • 51
  • Thanks! This version worked for me but the latest version of Selenium uses: driver: driver.FindElement(By.XPath... – Nomad77 May 13 '23 at 16:30
17

You can also use it with Page Object Pattern, e.g:

Try this code:

@FindBy(xpath = "//*[contains(text(), 'Best Choice')]")
WebElement buttonBestChoice;
iamsankalp89
  • 4,607
  • 2
  • 15
  • 36
9

Interestingly virtually all answers revolve around XPath's function contains(), neglecting the fact it is case sensitive - contrary to the OP's ask.

If you need case insensitivity, that is achievable in XPath 1.0 (the version contemporary browsers support), though it's not pretty - by using the translate() function. It substitutes a source character to its desired form, by using a translation table.

Constructing a table of all upper case characters will effectively transform the node's text to its lower() form - allowing case-insensitive matching (here's just the prerogative):

[
  contains(
    translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),
    'my button'
  )
]
# will match a source text like "mY bUTTon"

The full Python call:

driver.find_elements_by_xpath("//*[contains(translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZЙ', 'abcdefghijklmnopqrstuvwxyzй'), 'my button')]")

Naturally this approach has its drawbacks - as given, it'll work only for Latin text; if you want to cover Unicode characters - you'll have to add them to the translation table. I've done that in the sample above - the last character is the Cyrillic symbol "Й".


And if we lived in a world where browsers supported XPath 2.0 and up (, but not happening any time soon ☹️), we could having used the functions lower-case() (yet, not fully locale-aware), and matches (for regex searches, with the case-insensitive ('i') flag).

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Todor Minakov
  • 19,097
  • 3
  • 55
  • 60
6

Simply use This:

driver.find_elements_by_xpath('//*[text() = "My Button"]')
Amar Kumar
  • 2,392
  • 2
  • 25
  • 33
3

Similar problem: Find <button>Advanced...</button>

Maybe this will give you some ideas (please transfer the concept from Java to Python):

wait.until(ExpectedConditions.elementToBeClickable(//
    driver.findElements(By.tagName("button")).stream().filter(i -> i.getText().equals("Advanced...")).findFirst().get())).click();
Reto Höhener
  • 5,419
  • 4
  • 39
  • 79
2

Text Containing

To find an element with the text (displayed in the HTML page) containing a specific text, you can use the following XPath expression:

driver.find_element(By.XPATH, "//*[contains(text(), 'text_to_be_contained')]")
  • '*', selects all elements in the document, regardless of the tag name. You can replace the asterisk (*) with a specific tag name if you want to search within a particular tag type.
  • [contains(text(), 'text_to_be_contained')] is a condition that checks if the text contains the specified text ('text_to_be_contained'). This will match elements whose text attribute contains the specified text, even if it is part of a larger text value.
  • 'text_to_be_contained', should be the text you want to find

Attribute Containing

To find an element with the attribute containing a specific text, you can use the following XPath expression:

//*[contains(@attribute_name, 'text_to_be_contained')]
  • Replace 'attribute_name' with the actual name of the attribute you want to search within, such as: class, name, href, value, or any other

Text/Attribute Is Equal

To find an element with the text or attribute a matching exactly a specific text, you can use the following XPath expressions: For the text

For the text:

//*[text()='exact_text']

For the attribute:

//*[@attribute_name='exact_text']
  • 'exact_text', should be the text you want to find

Note: All methods are case sensitive

PhPires13
  • 62
  • 5
1
wait.until(ExpectedConditions.visibilityOfElementLocated(By.xpath("//*[contains(text(), 'YourTextHere')]")));
assertNotNull(driver.findElement(By.xpath("//*[contains(text(), 'YourTextHere')]")));
String yourButtonName = driver.findElement(By.xpath("//*[contains(text(), 'YourTextHere')]")).getAttribute("innerText");
assertTrue(yourButtonName.equalsIgnoreCase("YourTextHere"));
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
mike oganyan
  • 137
  • 5
1

In Selenium 4 find_element_by_xpath is deprecated. See the docs, https://selenium-python.readthedocs.io/locating-elements.html

Here are two options:

from selenium import webdriver
from selenium.webdriver.firefox.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
service = Service('D:\\Path\\to\\geckodriver.exe')
driver = webdriver.Firefox(service=service)

element = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, "//*[contains(text(), 'My Button')]")))

OR

element = driver.find_element(By.XPATH, "//*[contains(text(), 'My Button')]")
Jortega
  • 3,616
  • 1
  • 18
  • 21
0

Use driver.find_elements_by_xpath and matches regex matching function for the case insensitive search of the element by its text.

driver.find_elements_by_xpath("//*[matches(.,'My Button', 'i')]")
Andriy Ivaneyko
  • 20,639
  • 6
  • 60
  • 82
-20

Try this. It's very easy:

driver.getPageSource().contains("text to search");

This really worked for me in Selenium WebDriver.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Amit
  • 178
  • 1
  • 6
  • 13
    It does not work if the text is generated by JavaScript. – palacsint Feb 08 '14 at 21:27
  • 3
    This is a very way of checking it, because you are transferring the whole contents of the page over the wire. For very small pages this is acceptablem but for very large pages you are transfer all the contents of the file and checking on the server side. A better approach would be to do it on the client side with xpath, javascript or css. – thomas.han Aug 25 '14 at 08:22
  • 1
    I would think that the whole page source would already need to be transferred over the wire for the browser to render it? – René Sep 05 '14 at 18:31
  • 3
    Josh is asking how to find the element by text, not to test if the text is present in the source of the page. – Cedric May 28 '15 at 17:19
  • 1
    For instances where all is needed is to find a static text on a page this solution is good enough. (It helped in my case). – Karlth Sep 20 '15 at 23:00
  • Bad way to do in entire page source! – Jitesh Sojitra Sep 03 '16 at 06:56